mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-08-18 15:33:33 +08:00
feat.AirApp重构工程
This commit is contained in:
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -227,9 +227,9 @@ jobs:
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
pack-plugin-packages:
|
||||
pack-airapp-packages:
|
||||
runs-on: ubuntu-latest
|
||||
name: Pack_Plugin_Packages
|
||||
name: Pack_AirApp_Packages
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
shell: pwsh
|
||||
run: .\scripts\Pack-PluginPackages.ps1 -Configuration Release -OutputPath .\artifacts\nuget
|
||||
|
||||
- name: Upload plugin package artifacts
|
||||
- name: Upload AirApp package artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: plugin-packages
|
||||
|
||||
24
AGENTS.md
24
AGENTS.md
@@ -4,10 +4,10 @@
|
||||
|
||||
## 1. 项目目标与仓库边界
|
||||
|
||||
- 本仓库是阑山桌面桌面宿主、宿主侧插件运行时、Plugin SDK、共享契约与基础外观/设置能力的权威来源。
|
||||
- 不要把插件市场元数据、开发者门户或官方示例插件实现当作本仓库内容维护。
|
||||
- 本仓库是阑山桌面桌面宿主、宿主侧 AirApp 运行时、AirApp SDK、共享契约与基础外观/设置能力的权威来源。
|
||||
- 不要把轻应用市场元数据、开发者门户或官方示例轻应用实现当作本仓库内容维护。
|
||||
- 市场和生态材料属于兄弟仓库 `LanAirApp`。
|
||||
- 官方示例插件属于独立仓库 `LanMountainDesktop.SamplePlugin`。
|
||||
- 官方示例轻应用属于独立仓库 `LanMountainDesktop.SamplePlugin`。
|
||||
|
||||
边界详情看:
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
## 2. 关键目录地图
|
||||
|
||||
- `desktop/LanMountainDesktop/`: 主宿主应用,包含 UI、服务、组件系统、主题与插件运行时接入
|
||||
- `desktop/LanMountainDesktop/`: 主宿主应用,包含 UI、服务、组件系统、主题与 AirApp 运行时接入
|
||||
- `desktop/LanMountainDesktop/ComponentSystem/`: 内置组件定义、注册、扩展加载
|
||||
- `desktop/LanMountainDesktop/plugins/`: 宿主侧插件运行时、安装与 market 集成
|
||||
- `desktop/LanMountainDesktop/plugins/`: 宿主侧 AirApp 运行时、安装与 market 集成
|
||||
- `desktop/LanMountainDesktop/Views/` and `ViewModels/`: UI 页面、窗口与视图模型
|
||||
- `desktop/LanMountainDesktop/Services/`: 设置、遥测、启动、持久化、业务服务
|
||||
- `airapp/LanMountainDesktop.PluginSdk/`: 插件 SDK 公共接口和默认打包行为(含 PluginIsolation、Host.Abstractions、Settings.Core、Appearance)
|
||||
- `airapp/LanMountainDesktop.AirAppSdk/`: AirApp SDK 公共接口和默认打包行为(含隔离层与宿主桥接契约)
|
||||
- `platform/LanMountainDesktop.Platform/`: 平台差异层(接口 + Windows/macOS 实现)
|
||||
- `core/LanMountainDesktop.Core/`: 宿主/插件共享契约、IPC 基础设施与插件打包
|
||||
- `core/LanMountainDesktop.Core/`: 宿主/AirApp 共享契约、IPC 基础设施与打包
|
||||
- `mobile/LanMountainDesktop.Mobile/`: 共享移动 UI 壳(组件面板)
|
||||
- `mobile/LanMountainDesktop.Mobile.Android/`: Android head(入口)
|
||||
- `tests/LanMountainDesktop.Tests/`: 宿主与 SDK 测试
|
||||
@@ -40,7 +40,7 @@ dotnet run --project desktop/LanMountainDesktop/LanMountainDesktop.csproj
|
||||
dotnet test LanMountainDesktop.slnx -c Debug
|
||||
```
|
||||
|
||||
插件本地包生成:
|
||||
AirApp 本地包生成:
|
||||
|
||||
```powershell
|
||||
./scripts/Pack-PluginPackages.ps1
|
||||
@@ -72,17 +72,17 @@ dotnet test LanMountainDesktop.slnx -c Debug
|
||||
- 设置页相关改动通常同时落在 `Views/`、`ViewModels/`、`Services/` 和 `.trae/specs/`
|
||||
- UI 启动与窗口生命周期主线在 `Program.cs` 和 `App.axaml.cs`
|
||||
|
||||
### 插件
|
||||
### AirApp
|
||||
|
||||
- SDK 公共 API 以 `airapp/LanMountainDesktop.PluginSdk/` 为准
|
||||
- SDK 公共 API 以 `airapp/LanMountainDesktop.AirAppSdk/` 为准
|
||||
- 共享契约以 `core/LanMountainDesktop.Core/` 为准
|
||||
- market 数据来源默认是兄弟仓库 `..\\LanAirApp`
|
||||
- 迁移或 breaking change 优先同步 `docs/PLUGIN_SDK_V5_MIGRATION.md`
|
||||
|
||||
### 设置与主题
|
||||
|
||||
- 设置持久化和 scope 变化优先检查 `airapp/LanMountainDesktop.PluginSdk/`(合并自 `LanMountainDesktop.Settings.Core`)
|
||||
- 外观、圆角、主题资源优先检查 `airapp/LanMountainDesktop.PluginSdk/`(合并自 `LanMountainDesktop.Appearance`)与专题规范
|
||||
- 设置持久化和 scope 变化优先检查 `airapp/LanMountainDesktop.AirAppSdk/`(合并自 `LanMountainDesktop.Settings.Core`)
|
||||
- 外观、圆角、主题资源优先检查 `airapp/LanMountainDesktop.AirAppSdk/`(合并自 `LanMountainDesktop.Appearance`)与专题规范
|
||||
- **圆角统一**:桌面组件(Widget)必须统一使用动态资源 `DesignCornerRadiusComponent`。严禁在组件根容器使用硬编码数值或非组件级令牌(如 `Xs`, `Md` 等),以确保全局圆角缩放设置能正确应用到所有组件。
|
||||
|
||||
## 6. 权威来源
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<Solution>
|
||||
<Project Path="core/LanMountainDesktop.Core/LanMountainDesktop.Core.csproj" />
|
||||
<Project Path="platform/LanMountainDesktop.Platform/LanMountainDesktop.Platform.csproj" />
|
||||
<Project Path="airapp/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj" />
|
||||
<Project Path="airapp/LanMountainDesktop.AirAppSdk/LanMountainDesktop.AirAppSdk.csproj" />
|
||||
<Project Path="airapp/LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj" />
|
||||
<Project Path="airapp/LanMountainDesktop.AirAppHost/LanMountainDesktop.AirAppHost.csproj" />
|
||||
<Project Path="airapp/LanMountainDesktop.PluginTemplate/LanMountainDesktop.PluginTemplate.csproj" />
|
||||
<Project Path="airapp/LanMountainDesktop.AirAppDevServer/LanMountainDesktop.AirAppDevServer.csproj" />
|
||||
<Project Path="airapp/LanMountainDesktop.AirAppTemplate/LanMountainDesktop.AirAppTemplate.csproj" />
|
||||
<Project Path="desktop/LanMountainDesktop/LanMountainDesktop.csproj" />
|
||||
<Project Path="desktop/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj" />
|
||||
<Project Path="install/LanDesktopPLONDS.installer/LanDesktopPLONDS.installer.csproj" />
|
||||
|
||||
49
README.md
49
README.md
@@ -37,10 +37,10 @@
|
||||
- 多页桌面,工作学习场景分离
|
||||
- 主题切换、玻璃效果、圆角风格
|
||||
|
||||
### 🔌 插件生态
|
||||
- 通过 `.laapp` 插件扩展功能
|
||||
- 官方 Plugin SDK 支持自定义组件
|
||||
- 设置页、组件、集成功能一站式接入
|
||||
### 🔌 轻应用生态
|
||||
- 通过 `.laapp` 轻应用扩展功能
|
||||
- 官方 AirApp SDK 支持桌面组件与窗口轻应用
|
||||
- 设置页、组件、窗口、集成功能一站式接入
|
||||
|
||||
## 为谁而设计
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|---------|---------|
|
||||
| 🎓 学生用户 | 课程表、自习监测、计时、天气和日常信息聚合 |
|
||||
| 💼 办公用户 | 日历、资讯、最近文档、常用工具入口 |
|
||||
| 🎨 效率爱好者 | 自由布局、主题切换、插件扩展 |
|
||||
| 🎨 效率爱好者 | 自由布局、主题切换、轻应用扩展 |
|
||||
| 🇨🇳 中文用户 | 本地化界面、农历和节假日等本地语境支持 |
|
||||
|
||||
## 快速开始
|
||||
@@ -84,37 +84,36 @@ dotnet run --project LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csp
|
||||
dotnet test LanMountainDesktop.slnx -c Debug
|
||||
```
|
||||
|
||||
## 插件开发
|
||||
## 轻应用开发
|
||||
|
||||
阑山桌面支持通过 Plugin SDK 开发自定义插件:
|
||||
阑山桌面通过统一的 AirApp SDK 支持轻应用扩展(桌面组件 + 窗口轻应用):
|
||||
|
||||
```bash
|
||||
# 安装插件模板
|
||||
dotnet new install LanMountainDesktop.PluginTemplate
|
||||
# 安装轻应用模板
|
||||
dotnet new install LanMountainDesktop.AirAppTemplate
|
||||
|
||||
# 创建新插件
|
||||
dotnet new lmd-plugin -n MyPlugin
|
||||
# 创建新轻应用
|
||||
dotnet new lmd-airapp -n MyAirApp
|
||||
```
|
||||
|
||||
- **Plugin SDK**: `LanMountainDesktop.PluginSdk` (API 6.0.0)
|
||||
- **AirApp SDK**: `LanMountainDesktop.AirAppSdk` (API 1.0.0)
|
||||
- **共享契约**: `LanMountainDesktop.Core`
|
||||
- **迁移指南**: [PLUGIN_SDK_V5_MIGRATION.md](docs/PLUGIN_SDK_V5_MIGRATION.md)
|
||||
- **迁移指南**: [AIRAPP_SDK_V1_MIGRATION.md](docs/AIRAPP_SDK_V1_MIGRATION.md)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
LanMountainDesktop/
|
||||
├── core/ # 共享基础:契约、IPC、插件打包
|
||||
├── core/ # 共享基础:契约、IPC、打包
|
||||
│ └── LanMountainDesktop.Core/
|
||||
├── platform/ # 平台差异层(接口 + Windows/macOS 实现)
|
||||
│ └── LanMountainDesktop.Platform/
|
||||
├── airappsdk/ # AirApp 第三方 SDK 与工具
|
||||
│ └── LanMountainDesktop.AirAppSdk/ (+ AirAppDevServer / AirAppTemplate)
|
||||
├── airapp/ # 插件 SDK 与 AirApp 运行时
|
||||
│ ├── LanMountainDesktop.PluginSdk/ # 官方插件 SDK
|
||||
│ ├── LanMountainDesktop.AirAppRuntime/
|
||||
│ ├── LanMountainDesktop.AirAppHost/
|
||||
│ └── LanMountainDesktop.PluginTemplate/
|
||||
├── airapp/ # AirApp 统一扩展系统
|
||||
│ ├── LanMountainDesktop.AirAppSdk/ # 统一 SDK(组件 + 窗口轻应用)
|
||||
│ ├── LanMountainDesktop.AirAppRuntime/ # 轻应用运行时进程
|
||||
│ ├── LanMountainDesktop.AirAppHost/ # 轻应用窗口宿主进程
|
||||
│ ├── LanMountainDesktop.AirAppTemplate/ # dotnet new lmd-airapp 模板
|
||||
│ └── LanMountainDesktop.AirAppDevServer/ # 轻应用开发工具
|
||||
├── desktop/ # 桌面宿主
|
||||
│ ├── LanMountainDesktop/ # 桌面宿主应用
|
||||
│ └── LanMountainDesktop.Launcher/ # 启动器 (OOBE、Splash、版本管理、更新)
|
||||
@@ -131,15 +130,17 @@ LanMountainDesktop/
|
||||
|
||||
| 项目 | 职责 |
|
||||
|-----|------|
|
||||
| **本仓库** | 桌面宿主、插件运行时、Plugin SDK、共享契约 |
|
||||
| [LanAirApp](https://github.com/yourorg/LanAirApp) | 插件市场元数据、开发者生态材料 |
|
||||
| [LanMountainDesktop.SamplePlugin](https://github.com/yourorg/LanMountainDesktop.SamplePlugin) | 官方示例插件 |
|
||||
| **本仓库** | 桌面宿主、AirApp 运行时、AirApp SDK、共享契约 |
|
||||
| [LanAirApp](https://github.com/yourorg/LanAirApp) | 轻应用市场元数据、开发者生态材料 |
|
||||
| [LanMountainDesktop.SamplePlugin](https://github.com/yourorg/LanMountainDesktop.SamplePlugin) | 官方示例轻应用 |
|
||||
|
||||
## 文档索引
|
||||
|
||||
- [产品定位](docs/PRODUCT.md) - 产品愿景与目标用户
|
||||
- [架构说明](docs/ARCHITECTURE.md) - 仓库结构与运行时主线
|
||||
- [开发指南](docs/DEVELOPMENT.md) - 构建、测试、调试
|
||||
- [AirApp 开发指南](docs/01-AirApp开发/README.md) - 轻应用组件与窗口开发
|
||||
- [AirApp 迁移指南](docs/AIRAPP_SDK_V1_MIGRATION.md) - 插件迁移到 AirApp
|
||||
- [视觉规范](docs/VISUAL_SPEC.md) - 主题、颜色、玻璃层级
|
||||
- [圆角规范](docs/CORNER_RADIUS_SPEC.md) - 圆角层级与动态规则
|
||||
- [贡献指南](docs/CONTRIBUTING.md) - PR、spec、文档协作规则
|
||||
|
||||
@@ -8,7 +8,8 @@ public sealed record AirAppLaunchOptions(
|
||||
string? LauncherPipeName,
|
||||
string? InstanceKey,
|
||||
string? DataRoot,
|
||||
string? TargetEntryId)
|
||||
string? TargetEntryId,
|
||||
string? AppPackagePath)
|
||||
{
|
||||
public const string WorldClockAppId = "world-clock";
|
||||
public const string WhiteboardAppId = "whiteboard";
|
||||
@@ -63,7 +64,8 @@ public sealed record AirAppLaunchOptions(
|
||||
GetOptionalValue(values, "launcher-pipe"),
|
||||
GetOptionalValue(values, "instance-key"),
|
||||
GetOptionalValue(values, "data-root"),
|
||||
GetOptionalValue(values, "target-entry-id"));
|
||||
GetOptionalValue(values, "target-entry-id"),
|
||||
GetOptionalValue(values, "app-package"));
|
||||
}
|
||||
|
||||
private static string GetValue(IReadOnlyDictionary<string, string> values, string key, string fallback)
|
||||
|
||||
@@ -17,6 +17,8 @@ public sealed partial class AirAppWindow : FAAppWindow
|
||||
private readonly AirAppWindowDescriptor _descriptor;
|
||||
private WhiteboardWidget? _whiteboardWidget;
|
||||
private string _instanceKey = string.Empty;
|
||||
private AirAppWindowLoader.LoadedAirAppWindow? _loadedAirAppWindow;
|
||||
private LanMountainDesktop.AirAppSdk.IAirAppWindow? _thirdPartyWindow;
|
||||
|
||||
public AirAppWindow()
|
||||
: this(AirAppLaunchOptions.Parse([]))
|
||||
@@ -61,6 +63,12 @@ public sealed partial class AirAppWindow : FAAppWindow
|
||||
|
||||
private void ConfigureWindow()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_options.AppPackagePath))
|
||||
{
|
||||
ConfigureThirdPartyWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyWindowDescriptor(_descriptor);
|
||||
|
||||
if (string.Equals(_options.AppId, AirAppLaunchOptions.WorldClockAppId, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -88,6 +96,90 @@ public sealed partial class AirAppWindow : FAAppWindow
|
||||
};
|
||||
}
|
||||
|
||||
private void ConfigureThirdPartyWindow()
|
||||
{
|
||||
var loaded = new AirAppWindowLoader().Load(_options);
|
||||
_loadedAirAppWindow = loaded;
|
||||
_thirdPartyWindow = loaded.Window;
|
||||
var window = loaded.Window;
|
||||
|
||||
ApplySdkWindowDescriptor(window.Descriptor);
|
||||
|
||||
var content = window.Content;
|
||||
if (content is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp window '{_options.AppId}' exposes no Content. Set Content on the IAirAppWindow implementation.");
|
||||
}
|
||||
|
||||
ContentHost.Content = content;
|
||||
_ = window.OnWindowOpeningAsync();
|
||||
AppLogger.Info(
|
||||
"AirAppWindow",
|
||||
$"Third-party AirApp window configured. AppId='{_options.AppId}'; TargetEntryId='{_options.TargetEntryId ?? string.Empty}'.");
|
||||
}
|
||||
|
||||
private void ApplySdkWindowDescriptor(LanMountainDesktop.AirAppSdk.AirAppWindowDescriptor descriptor)
|
||||
{
|
||||
Title = string.IsNullOrWhiteSpace(descriptor.Title) ? _options.AppId : descriptor.Title;
|
||||
Width = descriptor.Width;
|
||||
Height = descriptor.Height;
|
||||
MinWidth = descriptor.MinWidth;
|
||||
MinHeight = descriptor.MinHeight;
|
||||
ShowInTaskbar = descriptor.ShowInTaskbar;
|
||||
CanResize = descriptor.CanResize;
|
||||
ShowAsDialog = descriptor.ShowAsDialog;
|
||||
WindowState = WindowState.Normal;
|
||||
WindowRoot.Background = this.TryFindResource("AirAppWindowBackgroundBrush", out var brush) && brush is IBrush backgroundBrush
|
||||
? backgroundBrush
|
||||
: Brushes.White;
|
||||
|
||||
switch (descriptor.ChromeMode)
|
||||
{
|
||||
case LanMountainDesktop.AirAppSdk.AirAppWindowChromeMode.Standard:
|
||||
WindowDecorations = WindowDecorations.Full;
|
||||
TitleBar.ExtendsContentIntoTitleBar = false;
|
||||
TitleBar.Height = 40;
|
||||
break;
|
||||
|
||||
case LanMountainDesktop.AirAppSdk.AirAppWindowChromeMode.Borderless:
|
||||
WindowDecorations = WindowDecorations.None;
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
TitleBar.Height = 40;
|
||||
break;
|
||||
|
||||
case LanMountainDesktop.AirAppSdk.AirAppWindowChromeMode.FullScreen:
|
||||
WindowDecorations = WindowDecorations.None;
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
ShowAsDialog = false;
|
||||
WindowState = WindowState.FullScreen;
|
||||
TitleBar.Height = 40;
|
||||
break;
|
||||
|
||||
case LanMountainDesktop.AirAppSdk.AirAppWindowChromeMode.Tool:
|
||||
WindowDecorations = WindowDecorations.Full;
|
||||
TitleBar.ExtendsContentIntoTitleBar = false;
|
||||
ShowInTaskbar = false;
|
||||
CanResize = false;
|
||||
TitleBar.Height = 36;
|
||||
break;
|
||||
|
||||
case LanMountainDesktop.AirAppSdk.AirAppWindowChromeMode.BackgroundOnly:
|
||||
// Reserved for future background-only Air APPs.
|
||||
break;
|
||||
}
|
||||
|
||||
TitleBar.BackgroundColor = Colors.Transparent;
|
||||
TitleBar.ForegroundColor = Color.FromRgb(32, 32, 32);
|
||||
TitleBar.InactiveBackgroundColor = Colors.Transparent;
|
||||
TitleBar.InactiveForegroundColor = Color.FromRgb(96, 96, 96);
|
||||
TitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(23, 0, 0, 0);
|
||||
TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(52, 0, 0, 0);
|
||||
TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
TitleBar.ButtonInactiveForegroundColor = Colors.Gray;
|
||||
}
|
||||
|
||||
private void ApplyWindowDescriptor(AirAppWindowDescriptor descriptor)
|
||||
{
|
||||
Title = descriptor.Title;
|
||||
@@ -180,6 +272,7 @@ public sealed partial class AirAppWindow : FAAppWindow
|
||||
{
|
||||
base.OnOpened(e);
|
||||
_ = RegisterWithLauncherAsync();
|
||||
_thirdPartyWindow?.OnWindowOpened();
|
||||
AppLogger.Info(
|
||||
"AirAppWindow",
|
||||
$"Opened. WindowRole=AirApp; AppId='{_options.AppId}'; ForegroundActivationRequested=True.");
|
||||
@@ -192,16 +285,47 @@ public sealed partial class AirAppWindow : FAAppWindow
|
||||
protected override void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
SaveWhiteboard();
|
||||
_thirdPartyWindow?.OnWindowClosing(e);
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
SaveAndDisposeWhiteboard();
|
||||
DisposeLoadedAirAppWindow();
|
||||
_ = UnregisterWithLauncherAsync();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
private void DisposeLoadedAirAppWindow()
|
||||
{
|
||||
var loaded = _loadedAirAppWindow;
|
||||
if (loaded is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_loadedAirAppWindow = null;
|
||||
_thirdPartyWindow = null;
|
||||
try
|
||||
{
|
||||
loaded.Window.OnWindowClosed();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("AirAppWindow", "Failed to notify AirApp window closed.", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
loaded.LoadedAirApp.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("AirAppWindow", "Failed to dispose loaded AirApp window.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveAndDisposeWhiteboard()
|
||||
{
|
||||
var widget = _whiteboardWidget;
|
||||
|
||||
130
airapp/LanMountainDesktop.AirAppHost/AirAppWindowLoader.cs
Normal file
130
airapp/LanMountainDesktop.AirAppHost/AirAppWindowLoader.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using LanMountainDesktop.AirAppSdk;
|
||||
using LanMountainDesktop.AirApps;
|
||||
using LanMountainDesktop.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LanMountainDesktop.AirAppHost;
|
||||
|
||||
/// <summary>
|
||||
/// Loads a third-party AirApp window into the AirAppHost process.
|
||||
/// Reads the AirApp package manifest, loads the entrance assembly with the desktop
|
||||
/// <see cref="AirAppLoader"/>, resolves the requested window by id, and produces the
|
||||
/// window "blueprint" (content + descriptor) to be hosted inside the FAAppWindow shell.
|
||||
/// </summary>
|
||||
internal sealed class AirAppWindowLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of loading a third-party AirApp window.
|
||||
/// </summary>
|
||||
internal sealed record LoadedAirAppWindow(
|
||||
IAirAppWindow Window,
|
||||
LoadedAirApp LoadedAirApp);
|
||||
|
||||
public LoadedAirAppWindow Load(AirAppLaunchOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.AppPackagePath))
|
||||
{
|
||||
throw new InvalidOperationException("AirAppWindowLoader requires an --app-package path.");
|
||||
}
|
||||
|
||||
var packageDirectory = Path.GetFullPath(options.AppPackagePath);
|
||||
if (!Directory.Exists(packageDirectory))
|
||||
{
|
||||
throw new InvalidOperationException($"AirApp package directory was not found: '{packageDirectory}'.");
|
||||
}
|
||||
|
||||
var manifestPath = Path.Combine(packageDirectory, AirAppSdkInfo.ManifestFileName);
|
||||
if (!File.Exists(manifestPath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp package directory does not contain '{AirAppSdkInfo.ManifestFileName}': '{packageDirectory}'.");
|
||||
}
|
||||
|
||||
var manifest = AirAppManifest.Load(manifestPath);
|
||||
var assemblyPath = manifest.ResolveEntranceAssemblyPath(manifestPath);
|
||||
if (!File.Exists(assemblyPath))
|
||||
{
|
||||
throw new InvalidOperationException($"AirApp entrance assembly was not found: '{assemblyPath}'.");
|
||||
}
|
||||
|
||||
var loader = new AirAppLoader();
|
||||
var loadResult = loader.LoadFromAssembly(assemblyPath, manifest);
|
||||
if (!loadResult.IsSuccess || loadResult.LoadedAirApp is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to load AirApp '{manifest.Id}': {loadResult.Error?.Message ?? "unknown error"}",
|
||||
loadResult.Error);
|
||||
}
|
||||
|
||||
var loadedAirApp = loadResult.LoadedAirApp;
|
||||
try
|
||||
{
|
||||
var windowId = ResolveWindowId(options, manifest, loadedAirApp);
|
||||
var windowRegistration = loadedAirApp.Services
|
||||
.GetServices<AirAppWindowRegistration>()
|
||||
.FirstOrDefault(registration => string.Equals(registration.WindowId, windowId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (windowRegistration is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp '{manifest.Id}' does not declare a window with id '{windowId}'. " +
|
||||
"Register windows with AddAirAppWindow<TWindow> in Initialize().");
|
||||
}
|
||||
|
||||
var window = CreateWindow(loadedAirApp, windowRegistration);
|
||||
AppLogger.Info(
|
||||
"AirAppWindow",
|
||||
$"Loaded third-party AirApp window. AirAppId='{manifest.Id}'; WindowId='{windowId}'; WindowType='{windowRegistration.WindowType.FullName}'.");
|
||||
return new LoadedAirAppWindow(window, loadedAirApp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
loadedAirApp.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveWindowId(
|
||||
AirAppLaunchOptions options,
|
||||
AirAppManifest manifest,
|
||||
LoadedAirApp loadedAirApp)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(options.TargetEntryId))
|
||||
{
|
||||
return options.TargetEntryId.Trim();
|
||||
}
|
||||
|
||||
var declaredWindows = loadedAirApp.Services.GetServices<AirAppWindowRegistration>().ToArray();
|
||||
if (declaredWindows.Length == 1)
|
||||
{
|
||||
return declaredWindows[0].WindowId;
|
||||
}
|
||||
|
||||
if (manifest.Windows is { Count: > 0 })
|
||||
{
|
||||
return manifest.Windows[0].Id;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp '{manifest.Id}' exposes no windows and no --target-entry-id was provided.");
|
||||
}
|
||||
|
||||
private static IAirAppWindow CreateWindow(LoadedAirApp loadedAirApp, AirAppWindowRegistration registration)
|
||||
{
|
||||
var instance = loadedAirApp.Services.GetService(registration.WindowType);
|
||||
instance ??= ActivatorUtilities.CreateInstance(loadedAirApp.Services, registration.WindowType);
|
||||
|
||||
if (instance is not IAirAppWindow window)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Window type '{registration.WindowType.FullName}' does not implement IAirAppWindow.");
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,8 @@ internal sealed class AirAppLifecycleService : IAirAppLifecycleService
|
||||
instanceKey,
|
||||
request.SourceComponentId,
|
||||
request.SourcePlacementId,
|
||||
request.TargetEntryId);
|
||||
request.TargetEntryId,
|
||||
request.AppPackagePath);
|
||||
if (process is null)
|
||||
{
|
||||
return Task.FromResult(BuildResult(false, "start_failed", "AirAppHost process was not created.", null));
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace LanMountainDesktop.AirAppRuntime;
|
||||
|
||||
internal interface IAirAppProcessStarter
|
||||
{
|
||||
Process? Start(string appId, string sessionId, string instanceKey, string? sourceComponentId, string? sourcePlacementId, string? targetEntryId = null);
|
||||
Process? Start(string appId, string sessionId, string instanceKey, string? sourceComponentId, string? sourcePlacementId, string? targetEntryId = null, string? appPackagePath = null);
|
||||
}
|
||||
|
||||
internal sealed class AirAppProcessStarter : IAirAppProcessStarter
|
||||
@@ -33,7 +33,8 @@ internal sealed class AirAppProcessStarter : IAirAppProcessStarter
|
||||
string instanceKey,
|
||||
string? sourceComponentId,
|
||||
string? sourcePlacementId,
|
||||
string? targetEntryId = null)
|
||||
string? targetEntryId = null,
|
||||
string? appPackagePath = null)
|
||||
{
|
||||
var hostPath = _locator.Resolve(_packageRootProvider(), _hostPathProvider());
|
||||
var startInfo = CreateStartInfo(hostPath);
|
||||
@@ -63,6 +64,11 @@ internal sealed class AirAppProcessStarter : IAirAppProcessStarter
|
||||
AddArgument(startInfo, "--target-entry-id", targetEntryId.Trim());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(appPackagePath))
|
||||
{
|
||||
AddArgument(startInfo, "--app-package", Path.GetFullPath(appPackagePath));
|
||||
}
|
||||
|
||||
AirAppRuntimeLogger.Info(
|
||||
$"Starting AirAppHost. AppId='{appId}'; InstanceKey='{instanceKey}'; HostPath='{hostPath}'; DataRoot='{dataRoot ?? string.Empty}'.");
|
||||
var process = Process.Start(startInfo);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 插件外观上下文实现,提供主题、圆角等外观资源的访问和变更通知。
|
||||
/// </summary>
|
||||
public sealed class PluginAppearanceContext : IPluginAppearanceContext
|
||||
public sealed class AirAppAppearanceContext : IAirAppAppearanceContext
|
||||
{
|
||||
private PluginAppearanceSnapshot _snapshot;
|
||||
private AirAppAppearanceSnapshot _snapshot;
|
||||
|
||||
/// <summary>
|
||||
/// 创建插件外观上下文实例。
|
||||
/// </summary>
|
||||
/// <param name="snapshot">初始外观快照</param>
|
||||
public PluginAppearanceContext(PluginAppearanceSnapshot snapshot)
|
||||
public AirAppAppearanceContext(AirAppAppearanceSnapshot snapshot)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(snapshot);
|
||||
ArgumentNullException.ThrowIfNull(snapshot.CornerRadiusTokens);
|
||||
@@ -25,7 +25,7 @@ public sealed class PluginAppearanceContext : IPluginAppearanceContext
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PluginAppearanceSnapshot Snapshot => _snapshot;
|
||||
public AirAppAppearanceSnapshot Snapshot => _snapshot;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AppearanceChangedEvent>? Changed;
|
||||
@@ -36,7 +36,7 @@ public sealed class PluginAppearanceContext : IPluginAppearanceContext
|
||||
/// </summary>
|
||||
/// <param name="newSnapshot">新的外观快照</param>
|
||||
/// <param name="changedProperties">变更的属性集合</param>
|
||||
public void UpdateSnapshot(PluginAppearanceSnapshot newSnapshot, IReadOnlyCollection<AppearanceProperty> changedProperties)
|
||||
public void UpdateSnapshot(AirAppAppearanceSnapshot newSnapshot, IReadOnlyCollection<AppearanceProperty> changedProperties)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(newSnapshot);
|
||||
ArgumentNullException.ThrowIfNull(changedProperties);
|
||||
@@ -69,7 +69,7 @@ public sealed class PluginAppearanceContext : IPluginAppearanceContext
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null)
|
||||
public double ResolveCornerRadius(AirAppCornerRadiusPreset preset, double? minimum = null, double? maximum = null)
|
||||
{
|
||||
var resolved = Math.Max(0d, _snapshot.CornerRadiusTokens.Get(preset));
|
||||
if (!minimum.HasValue && !maximum.HasValue)
|
||||
@@ -1,12 +1,12 @@
|
||||
using Avalonia;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class PluginAppearanceExtensions
|
||||
public static class AirAppAppearanceExtensions
|
||||
{
|
||||
public static CornerRadius ResolveCornerRadius(
|
||||
this PluginAppearanceSnapshot snapshot,
|
||||
PluginCornerRadiusPreset preset)
|
||||
this AirAppAppearanceSnapshot snapshot,
|
||||
AirAppCornerRadiusPreset preset)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(snapshot);
|
||||
var value = snapshot.CornerRadiusTokens.Get(preset);
|
||||
@@ -14,8 +14,8 @@ public static class PluginAppearanceExtensions
|
||||
}
|
||||
|
||||
public static CornerRadius ResolveCornerRadius(
|
||||
this PluginAppearanceSnapshot snapshot,
|
||||
PluginCornerRadiusPreset preset,
|
||||
this AirAppAppearanceSnapshot snapshot,
|
||||
AirAppCornerRadiusPreset preset,
|
||||
CornerRadius fallback)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(snapshot);
|
||||
@@ -28,8 +28,8 @@ public static class PluginAppearanceExtensions
|
||||
}
|
||||
|
||||
public static CornerRadius ResolveCornerRadius(
|
||||
this IPluginAppearanceContext context,
|
||||
PluginCornerRadiusPreset preset)
|
||||
this IAirAppAppearanceContext context,
|
||||
AirAppCornerRadiusPreset preset)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
var value = context.ResolveCornerRadius(preset);
|
||||
@@ -37,8 +37,8 @@ public static class PluginAppearanceExtensions
|
||||
}
|
||||
|
||||
public static CornerRadius ResolveCornerRadius(
|
||||
this IPluginAppearanceContext context,
|
||||
PluginCornerRadiusPreset preset,
|
||||
this IAirAppAppearanceContext context,
|
||||
AirAppCornerRadiusPreset preset,
|
||||
double minimum,
|
||||
double maximum)
|
||||
{
|
||||
@@ -48,7 +48,7 @@ public static class PluginAppearanceExtensions
|
||||
}
|
||||
|
||||
public static CornerRadius ResolveScaledCornerRadius(
|
||||
this IPluginAppearanceContext context,
|
||||
this IAirAppAppearanceContext context,
|
||||
double baseRadius)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
@@ -57,7 +57,7 @@ public static class PluginAppearanceExtensions
|
||||
}
|
||||
|
||||
public static CornerRadius ResolveScaledCornerRadius(
|
||||
this IPluginAppearanceContext context,
|
||||
this IAirAppAppearanceContext context,
|
||||
double baseRadius,
|
||||
double minimum,
|
||||
double maximum)
|
||||
@@ -68,8 +68,8 @@ public static class PluginAppearanceExtensions
|
||||
}
|
||||
|
||||
public static CornerRadius ResolveCornerRadius(
|
||||
this PluginDesktopComponentContext context,
|
||||
PluginCornerRadiusPreset preset,
|
||||
this AirAppComponentContext context,
|
||||
AirAppCornerRadiusPreset preset,
|
||||
double minimum,
|
||||
double maximum)
|
||||
{
|
||||
@@ -78,8 +78,8 @@ public static class PluginAppearanceExtensions
|
||||
return new CornerRadius(Math.Max(0d, value));
|
||||
}
|
||||
|
||||
public static PluginAppearanceSnapshot GetAppearanceSnapshot(
|
||||
this PluginDesktopComponentContext context)
|
||||
public static AirAppAppearanceSnapshot GetAppearanceSnapshot(
|
||||
this AirAppComponentContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.Appearance.Snapshot;
|
||||
@@ -1,11 +1,11 @@
|
||||
using Avalonia;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 插件外观辅助方法,提供统一的圆角和主题资源访问。
|
||||
/// </summary>
|
||||
public static class PluginAppearanceHelper
|
||||
public static class AirAppAppearanceHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取桌面组件主外壳圆角半径。
|
||||
@@ -13,10 +13,10 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>主外壳圆角半径(像素)</returns>
|
||||
public static double GetShellCornerRadius(this IPluginAppearanceContext context)
|
||||
public static double GetShellCornerRadius(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.ResolveCornerRadius(PluginCornerRadiusPreset.Component);
|
||||
return context.ResolveCornerRadius(AirAppCornerRadiusPreset.Component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -25,10 +25,10 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>内部卡片圆角半径(像素)</returns>
|
||||
public static double GetCardCornerRadius(this IPluginAppearanceContext context)
|
||||
public static double GetCardCornerRadius(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.ResolveCornerRadius(PluginCornerRadiusPreset.Sm);
|
||||
return context.ResolveCornerRadius(AirAppCornerRadiusPreset.Sm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,10 +37,10 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>控件圆角半径(像素)</returns>
|
||||
public static double GetControlCornerRadius(this IPluginAppearanceContext context)
|
||||
public static double GetControlCornerRadius(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.ResolveCornerRadius(PluginCornerRadiusPreset.Xs);
|
||||
return context.ResolveCornerRadius(AirAppCornerRadiusPreset.Xs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,10 +49,10 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>徽章圆角半径(像素)</returns>
|
||||
public static double GetBadgeCornerRadius(this IPluginAppearanceContext context)
|
||||
public static double GetBadgeCornerRadius(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.ResolveCornerRadius(PluginCornerRadiusPreset.Micro);
|
||||
return context.ResolveCornerRadius(AirAppCornerRadiusPreset.Micro);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,10 +61,10 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>中等面板圆角半径(像素)</returns>
|
||||
public static double GetMediumPanelCornerRadius(this IPluginAppearanceContext context)
|
||||
public static double GetMediumPanelCornerRadius(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.ResolveCornerRadius(PluginCornerRadiusPreset.Md);
|
||||
return context.ResolveCornerRadius(AirAppCornerRadiusPreset.Md);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,10 +73,10 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>大面板圆角半径(像素)</returns>
|
||||
public static double GetLargePanelCornerRadius(this IPluginAppearanceContext context)
|
||||
public static double GetLargePanelCornerRadius(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.ResolveCornerRadius(PluginCornerRadiusPreset.Lg);
|
||||
return context.ResolveCornerRadius(AirAppCornerRadiusPreset.Lg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,7 +85,7 @@ public static class PluginAppearanceHelper
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <param name="preset">圆角预设</param>
|
||||
/// <returns>Avalonia CornerRadius 结构</returns>
|
||||
public static CornerRadius ToCornerRadius(this IPluginAppearanceContext context, PluginCornerRadiusPreset preset)
|
||||
public static CornerRadius ToCornerRadius(this IAirAppAppearanceContext context, AirAppCornerRadiusPreset preset)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
var radius = context.ResolveCornerRadius(preset);
|
||||
@@ -97,7 +97,7 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>是否为暗色主题</returns>
|
||||
public static bool IsDarkTheme(this IPluginAppearanceContext context)
|
||||
public static bool IsDarkTheme(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return string.Equals(context.Snapshot.ThemeVariant, "Dark", StringComparison.OrdinalIgnoreCase);
|
||||
@@ -108,7 +108,7 @@ public static class PluginAppearanceHelper
|
||||
/// </summary>
|
||||
/// <param name="context">外观上下文</param>
|
||||
/// <returns>主题变体字符串("Light" 或 "Dark")</returns>
|
||||
public static string GetThemeVariant(this IPluginAppearanceContext context)
|
||||
public static string GetThemeVariant(this IAirAppAppearanceContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return context.Snapshot.ThemeVariant;
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <remarks>
|
||||
/// This is the runtime snapshot shape consumed by plugins inside the host process.
|
||||
/// It is intentionally distinct from the wire DTO with the same name in
|
||||
/// <c>LanMountainDesktop.PluginIsolation.Contracts.PluginAppearanceSnapshot</c>.
|
||||
/// <c>LanMountainDesktop.AirAppIsolation.Contracts.AirAppAppearanceSnapshot</c>.
|
||||
/// </remarks>
|
||||
public sealed record PluginMaterialSurfaceSnapshot(
|
||||
public sealed record AirAppMaterialSurfaceSnapshot(
|
||||
string BackgroundColor,
|
||||
string BorderColor,
|
||||
double BlurRadius,
|
||||
@@ -13,16 +13,16 @@ public sealed record PluginMaterialSurfaceSnapshot(
|
||||
|
||||
/// <remarks>
|
||||
/// Runtime-facing appearance snapshot for plugins. This is not the same contract as the
|
||||
/// wire-format snapshot in <c>LanMountainDesktop.PluginIsolation.Contracts</c>, even though the
|
||||
/// wire-format snapshot in <c>LanMountainDesktop.AirAppIsolation.Contracts</c>, even though the
|
||||
/// type name matches.
|
||||
/// </remarks>
|
||||
public sealed record PluginAppearanceSnapshot(
|
||||
PluginCornerRadiusTokens CornerRadiusTokens,
|
||||
public sealed record AirAppAppearanceSnapshot(
|
||||
AirAppCornerRadiusTokens CornerRadiusTokens,
|
||||
string ThemeVariant,
|
||||
string? AccentColor = null,
|
||||
string? SeedColor = null,
|
||||
string? ColorSource = null,
|
||||
string? SystemMaterialMode = null,
|
||||
IReadOnlyDictionary<string, string>? ColorRoles = null,
|
||||
IReadOnlyDictionary<string, PluginMaterialSurfaceSnapshot>? MaterialSurfaces = null,
|
||||
IReadOnlyDictionary<string, AirAppMaterialSurfaceSnapshot>? MaterialSurfaces = null,
|
||||
IReadOnlyList<string>? WallpaperSeedCandidates = null);
|
||||
@@ -5,13 +5,13 @@ namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for AirApp implementations.
|
||||
/// Inherit from this class and apply the [AirAppEntrance] attribute.
|
||||
/// Inherit from this class and apply the <see cref="AirAppEntranceAttribute"/> attribute.
|
||||
/// </summary>
|
||||
public abstract class AirAppBase : IAirApp
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the runtime context after the AirApp has started.
|
||||
/// Available after OnStartedAsync is called.
|
||||
/// Available after <see cref="OnStartedAsync"/> is called.
|
||||
/// </summary>
|
||||
protected IAirAppRuntimeContext? RuntimeContext { get; private set; }
|
||||
|
||||
@@ -69,9 +69,8 @@ public abstract class AirAppBase : IAirApp
|
||||
|
||||
var options = new AirAppComponentOptions
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
WidgetType = typeof(TWidget)
|
||||
ComponentId = id,
|
||||
DisplayName = name
|
||||
};
|
||||
|
||||
configure?.Invoke(options);
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace LanMountainDesktop.PluginIsolation.Contracts;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
public sealed record PluginCapabilityDeclaration(
|
||||
public sealed record AirAppCapabilityDeclaration(
|
||||
string Name,
|
||||
string Version,
|
||||
string? Description = null);
|
||||
|
||||
public static class PluginCapabilityNames
|
||||
public static class AirAppCapabilityNames
|
||||
{
|
||||
public const string Settings = "settings";
|
||||
public const string Appearance = "appearance";
|
||||
@@ -0,0 +1,11 @@
|
||||
using LanMountainDesktop.AirAppSdk;
|
||||
using LanMountainDesktop.Shared.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record AirAppComponentChromeContext(
|
||||
string ComponentId,
|
||||
string? PlacementId,
|
||||
double CellSize,
|
||||
AppearanceCornerRadiusTokens CornerRadiusTokens,
|
||||
AirAppSettingsScope Scope = AirAppSettingsScope.App);
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginDesktopComponentContext
|
||||
public sealed class AirAppComponentContext
|
||||
{
|
||||
public PluginDesktopComponentContext(
|
||||
PluginManifest manifest,
|
||||
public AirAppComponentContext(
|
||||
AirAppManifest manifest,
|
||||
string pluginDirectory,
|
||||
string dataDirectory,
|
||||
IServiceProvider services,
|
||||
@@ -11,8 +11,8 @@ public sealed class PluginDesktopComponentContext
|
||||
string componentId,
|
||||
string? placementId,
|
||||
double cellSize,
|
||||
IPluginAppearanceContext appearance,
|
||||
IPluginSettingsService? pluginSettings = null)
|
||||
IAirAppAppearanceContext appearance,
|
||||
IAirAppSettingsService? pluginSettings = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifest);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(pluginDirectory);
|
||||
@@ -23,7 +23,7 @@ public sealed class PluginDesktopComponentContext
|
||||
ArgumentNullException.ThrowIfNull(appearance);
|
||||
|
||||
Manifest = manifest;
|
||||
PluginDirectory = pluginDirectory;
|
||||
AirAppDirectory = pluginDirectory;
|
||||
DataDirectory = dataDirectory;
|
||||
Services = services;
|
||||
Properties = properties;
|
||||
@@ -31,12 +31,12 @@ public sealed class PluginDesktopComponentContext
|
||||
PlacementId = string.IsNullOrWhiteSpace(placementId) ? null : placementId.Trim();
|
||||
CellSize = Math.Max(1, cellSize);
|
||||
Appearance = appearance;
|
||||
PluginSettings = pluginSettings;
|
||||
AirAppSettings = pluginSettings;
|
||||
}
|
||||
|
||||
public PluginManifest Manifest { get; }
|
||||
public AirAppManifest Manifest { get; }
|
||||
|
||||
public string PluginDirectory { get; }
|
||||
public string AirAppDirectory { get; }
|
||||
|
||||
public string DataDirectory { get; }
|
||||
|
||||
@@ -50,22 +50,45 @@ public sealed class PluginDesktopComponentContext
|
||||
|
||||
public double CellSize { get; }
|
||||
|
||||
public IPluginAppearanceContext Appearance { get; }
|
||||
public IAirAppAppearanceContext Appearance { get; }
|
||||
|
||||
public PluginCornerRadiusTokens CornerRadiusTokens => Appearance.Snapshot.CornerRadiusTokens;
|
||||
public AirAppCornerRadiusTokens CornerRadiusTokens => Appearance.Snapshot.CornerRadiusTokens;
|
||||
|
||||
public IPluginSettingsService? PluginSettings { get; }
|
||||
public IAirAppSettingsService? AirAppSettings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 由宿主注入的窗口打开处理器。组件通过 <see cref="OpenWindowAsync"/> 打开本 AirApp 的窗口轻应用。
|
||||
/// </summary>
|
||||
public Func<string, Task>? OpenWindowHandler { get; set; }
|
||||
|
||||
public double ResolveScaledCornerRadius(double baseRadius, double? minimum = null, double? maximum = null)
|
||||
{
|
||||
return Appearance.ResolveScaledCornerRadius(baseRadius, minimum, maximum);
|
||||
}
|
||||
|
||||
public double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null)
|
||||
public double ResolveCornerRadius(AirAppCornerRadiusPreset preset, double? minimum = null, double? maximum = null)
|
||||
{
|
||||
return Appearance.ResolveCornerRadius(preset, minimum, maximum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开本 AirApp 声明的一个窗口轻应用。
|
||||
/// </summary>
|
||||
/// <param name="windowId">窗口标识(与 <c>AddAirAppWindow</c> 注册的 id 一致)。</param>
|
||||
public Task OpenWindowAsync(string windowId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(windowId);
|
||||
|
||||
var handler = OpenWindowHandler;
|
||||
if (handler is null)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"AirApp '{Manifest.Id}' component '{ComponentId}' requested to open window '{windowId}', but the host does not support window opening for this component.");
|
||||
}
|
||||
|
||||
return handler(windowId);
|
||||
}
|
||||
|
||||
public T? GetService<T>()
|
||||
{
|
||||
return (T?)Services.GetService(typeof(T));
|
||||
@@ -1,16 +1,16 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginDesktopComponentEditorContext
|
||||
public sealed class AirAppComponentEditorContext
|
||||
{
|
||||
public PluginDesktopComponentEditorContext(
|
||||
PluginManifest manifest,
|
||||
public AirAppComponentEditorContext(
|
||||
AirAppManifest manifest,
|
||||
string pluginDirectory,
|
||||
string dataDirectory,
|
||||
IServiceProvider services,
|
||||
IReadOnlyDictionary<string, object?> properties,
|
||||
string componentId,
|
||||
string? placementId,
|
||||
IPluginSettingsService? pluginSettings,
|
||||
IAirAppSettingsService? pluginSettings,
|
||||
IComponentEditorHostContext hostContext)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifest);
|
||||
@@ -22,19 +22,19 @@ public sealed class PluginDesktopComponentEditorContext
|
||||
ArgumentNullException.ThrowIfNull(hostContext);
|
||||
|
||||
Manifest = manifest;
|
||||
PluginDirectory = pluginDirectory;
|
||||
AirAppDirectory = pluginDirectory;
|
||||
DataDirectory = dataDirectory;
|
||||
Services = services;
|
||||
Properties = properties;
|
||||
ComponentId = componentId.Trim();
|
||||
PlacementId = string.IsNullOrWhiteSpace(placementId) ? null : placementId.Trim();
|
||||
PluginSettings = pluginSettings;
|
||||
AirAppSettings = pluginSettings;
|
||||
HostContext = hostContext;
|
||||
}
|
||||
|
||||
public PluginManifest Manifest { get; }
|
||||
public AirAppManifest Manifest { get; }
|
||||
|
||||
public string PluginDirectory { get; }
|
||||
public string AirAppDirectory { get; }
|
||||
|
||||
public string DataDirectory { get; }
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class PluginDesktopComponentEditorContext
|
||||
|
||||
public string? PlacementId { get; }
|
||||
|
||||
public IPluginSettingsService? PluginSettings { get; }
|
||||
public IAirAppSettingsService? AirAppSettings { get; }
|
||||
|
||||
public IComponentEditorHostContext HostContext { get; }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginDesktopComponentEditorRegistration
|
||||
public sealed class AirAppComponentEditorRegistration
|
||||
{
|
||||
public PluginDesktopComponentEditorRegistration(
|
||||
public AirAppComponentEditorRegistration(
|
||||
string componentId,
|
||||
Func<IServiceProvider, PluginDesktopComponentEditorContext, Control> editorFactory,
|
||||
Func<IServiceProvider, AirAppComponentEditorContext, Control> editorFactory,
|
||||
double preferredWidth = 720d,
|
||||
double preferredHeight = 540d,
|
||||
double minScale = 0.85d,
|
||||
@@ -44,9 +44,9 @@ public sealed class PluginDesktopComponentEditorRegistration
|
||||
AspectRatio = preferredWidth / preferredHeight;
|
||||
}
|
||||
|
||||
public PluginDesktopComponentEditorRegistration(
|
||||
public AirAppComponentEditorRegistration(
|
||||
string componentId,
|
||||
Func<PluginDesktopComponentEditorContext, Control> editorFactory,
|
||||
Func<AirAppComponentEditorContext, Control> editorFactory,
|
||||
double preferredWidth = 720d,
|
||||
double preferredHeight = 540d,
|
||||
double minScale = 0.85d,
|
||||
@@ -63,7 +63,7 @@ public sealed class PluginDesktopComponentEditorRegistration
|
||||
|
||||
public string ComponentId { get; }
|
||||
|
||||
public Func<IServiceProvider, PluginDesktopComponentEditorContext, Control> EditorFactory { get; }
|
||||
public Func<IServiceProvider, AirAppComponentEditorContext, Control> EditorFactory { get; }
|
||||
|
||||
public double PreferredWidth { get; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginDesktopComponentOptions
|
||||
public sealed class AirAppComponentOptions
|
||||
{
|
||||
public required string ComponentId { get; init; }
|
||||
|
||||
@@ -8,7 +8,7 @@ public sealed class PluginDesktopComponentOptions
|
||||
|
||||
public string IconKey { get; init; } = "PuzzlePiece";
|
||||
|
||||
public string Category { get; init; } = "Plugins";
|
||||
public string Category { get; init; } = "AirApps";
|
||||
|
||||
public int MinWidthCells { get; init; } = 2;
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class PluginDesktopComponentOptions
|
||||
|
||||
public bool AllowStatusBarPlacement { get; init; }
|
||||
|
||||
public PluginDesktopComponentResizeMode ResizeMode { get; init; } = PluginDesktopComponentResizeMode.Proportional;
|
||||
public AirAppComponentResizeMode ResizeMode { get; init; } = AirAppComponentResizeMode.Proportional;
|
||||
|
||||
public string? DisplayNameLocalizationKey { get; init; }
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class PluginDesktopComponentOptions
|
||||
|
||||
public string? DescriptionLocalizationKey { get; init; }
|
||||
|
||||
public PluginCornerRadiusPreset CornerRadiusPreset { get; init; } = PluginCornerRadiusPreset.Default;
|
||||
public AirAppCornerRadiusPreset CornerRadiusPreset { get; init; } = AirAppCornerRadiusPreset.Default;
|
||||
|
||||
public Func<IPluginAppearanceContext, double, double>? CornerRadiusResolver { get; init; }
|
||||
public Func<IAirAppAppearanceContext, double, double>? CornerRadiusResolver { get; init; }
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginDesktopComponentRegistration
|
||||
public sealed class AirAppComponentRegistration
|
||||
{
|
||||
public PluginDesktopComponentRegistration(
|
||||
Func<IServiceProvider, PluginDesktopComponentContext, Control> controlFactory,
|
||||
PluginDesktopComponentOptions options)
|
||||
public AirAppComponentRegistration(
|
||||
Func<IServiceProvider, AirAppComponentContext, Control> controlFactory,
|
||||
AirAppComponentOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(controlFactory);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
@@ -38,9 +38,9 @@ public sealed class PluginDesktopComponentRegistration
|
||||
CornerRadiusResolver = options.CornerRadiusResolver;
|
||||
}
|
||||
|
||||
public PluginDesktopComponentRegistration(
|
||||
Func<PluginDesktopComponentContext, Control> controlFactory,
|
||||
PluginDesktopComponentOptions options)
|
||||
public AirAppComponentRegistration(
|
||||
Func<AirAppComponentContext, Control> controlFactory,
|
||||
AirAppComponentOptions options)
|
||||
: this((_, context) => controlFactory(context), options)
|
||||
{
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public sealed class PluginDesktopComponentRegistration
|
||||
|
||||
public string? DescriptionLocalizationKey { get; }
|
||||
|
||||
public Func<IServiceProvider, PluginDesktopComponentContext, Control> ControlFactory { get; }
|
||||
public Func<IServiceProvider, AirAppComponentContext, Control> ControlFactory { get; }
|
||||
|
||||
public string IconKey { get; }
|
||||
|
||||
@@ -69,24 +69,24 @@ public sealed class PluginDesktopComponentRegistration
|
||||
|
||||
public bool AllowStatusBarPlacement { get; }
|
||||
|
||||
public PluginDesktopComponentResizeMode ResizeMode { get; }
|
||||
public AirAppComponentResizeMode ResizeMode { get; }
|
||||
|
||||
public PluginCornerRadiusPreset CornerRadiusPreset { get; }
|
||||
public AirAppCornerRadiusPreset CornerRadiusPreset { get; }
|
||||
|
||||
public Func<IPluginAppearanceContext, double, double>? CornerRadiusResolver { get; }
|
||||
public Func<IAirAppAppearanceContext, double, double>? CornerRadiusResolver { get; }
|
||||
|
||||
public double ResolveCornerRadius(IPluginAppearanceContext appearance, double cellSize)
|
||||
public double ResolveCornerRadius(IAirAppAppearanceContext appearance, double cellSize)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(appearance);
|
||||
|
||||
var resolved = CornerRadiusResolver is not null
|
||||
? CornerRadiusResolver(appearance, Math.Max(1d, cellSize))
|
||||
: CornerRadiusPreset == PluginCornerRadiusPreset.Default
|
||||
? appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Component)
|
||||
: CornerRadiusPreset == AirAppCornerRadiusPreset.Default
|
||||
? appearance.ResolveCornerRadius(AirAppCornerRadiusPreset.Component)
|
||||
: appearance.ResolveCornerRadius(CornerRadiusPreset);
|
||||
|
||||
return double.IsFinite(resolved)
|
||||
? Math.Max(0d, resolved)
|
||||
: appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Component);
|
||||
: appearance.ResolveCornerRadius(AirAppCornerRadiusPreset.Component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public enum AirAppComponentResizeMode
|
||||
{
|
||||
Proportional = 0,
|
||||
Free = 1
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public enum PluginCornerRadiusPreset
|
||||
public enum AirAppCornerRadiusPreset
|
||||
{
|
||||
Default = 0,
|
||||
Micro = 1,
|
||||
@@ -0,0 +1,52 @@
|
||||
using Avalonia;
|
||||
using LanMountainDesktop.Shared.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record AirAppCornerRadiusTokens(
|
||||
double Micro,
|
||||
double Xs,
|
||||
double Sm,
|
||||
double Md,
|
||||
double Lg,
|
||||
double Xl,
|
||||
double Island,
|
||||
double Component)
|
||||
{
|
||||
public double Get(AirAppCornerRadiusPreset preset)
|
||||
{
|
||||
return preset switch
|
||||
{
|
||||
AirAppCornerRadiusPreset.Default => Component,
|
||||
AirAppCornerRadiusPreset.Micro => Micro,
|
||||
AirAppCornerRadiusPreset.Xs => Xs,
|
||||
AirAppCornerRadiusPreset.Sm => Sm,
|
||||
AirAppCornerRadiusPreset.Md => Md,
|
||||
AirAppCornerRadiusPreset.Lg => Lg,
|
||||
AirAppCornerRadiusPreset.Xl => Xl,
|
||||
AirAppCornerRadiusPreset.Island => Island,
|
||||
AirAppCornerRadiusPreset.Component => Component,
|
||||
_ => Component
|
||||
};
|
||||
}
|
||||
|
||||
public CornerRadius ToCornerRadius(AirAppCornerRadiusPreset preset)
|
||||
{
|
||||
return new CornerRadius(Get(preset));
|
||||
}
|
||||
|
||||
public static AirAppCornerRadiusTokens FromShared(AppearanceCornerRadiusTokens tokens)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tokens);
|
||||
|
||||
return new AirAppCornerRadiusTokens(
|
||||
tokens.Micro.TopLeft,
|
||||
tokens.Xs.TopLeft,
|
||||
tokens.Sm.TopLeft,
|
||||
tokens.Md.TopLeft,
|
||||
tokens.Lg.TopLeft,
|
||||
tokens.Xl.TopLeft,
|
||||
tokens.Island.TopLeft,
|
||||
tokens.Component.TopLeft);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Marks a class as the entry point for an AirApp.
|
||||
/// The marked class must inherit from AirAppBase or implement IAirApp.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class AirAppEntranceAttribute : Attribute
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class PluginHostPropertyKeys
|
||||
public static class AirAppHostPropertyKeys
|
||||
{
|
||||
public const string HostApplicationName = "HostApplicationName";
|
||||
public const string HostVersion = "HostVersion";
|
||||
public const string PluginSdkApiVersion = "PluginSdkApiVersion";
|
||||
public const string AirAppSdkApiVersion = "AirAppSdkApiVersion";
|
||||
public const string HostLanguageCode = "HostLanguageCode";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record AirAppInstalledInfo(
|
||||
AirAppManifest Manifest,
|
||||
bool IsEnabled,
|
||||
bool IsLoaded,
|
||||
bool IsPackage,
|
||||
string? ErrorMessage);
|
||||
@@ -1,26 +1,26 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LanMountainDesktop.PluginIsolation.Ipc;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Ipc;
|
||||
|
||||
public sealed class PluginIpcClient
|
||||
public sealed class AirAppIpcClient
|
||||
{
|
||||
public PluginIpcClient(PluginIpcClientOptions options)
|
||||
public AirAppIpcClient(AirAppIpcClientOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
SerializerContext = options.SerializerContext ?? throw new ArgumentNullException(nameof(options.SerializerContext));
|
||||
SerializerOptions = SerializerContext.Options;
|
||||
}
|
||||
|
||||
public PluginIpcClientOptions Options { get; }
|
||||
public AirAppIpcClientOptions Options { get; }
|
||||
|
||||
public JsonSerializerContext SerializerContext { get; }
|
||||
|
||||
public JsonSerializerOptions SerializerOptions { get; }
|
||||
|
||||
public PluginIpcRequestDispatcher? RequestDispatcher { get; set; }
|
||||
public AirAppIpcRequestDispatcher? RequestDispatcher { get; set; }
|
||||
|
||||
public PluginIpcNotificationDispatcher? NotificationDispatcher { get; set; }
|
||||
public AirAppIpcNotificationDispatcher? NotificationDispatcher { get; set; }
|
||||
|
||||
public Task<TResponse?> RequestAsync<TRequest, TResponse>(
|
||||
string route,
|
||||
@@ -48,7 +48,7 @@ public sealed class PluginIpcClient
|
||||
if (RequestDispatcher is null)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"PluginIpcClient is not yet bound to a dotnetCampus.Ipc transport dispatcher. " +
|
||||
"AirAppIpcClient is not yet bound to a dotnetCampus.Ipc transport dispatcher. " +
|
||||
"Wire RequestDispatcher during host/worker transport integration.");
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed class PluginIpcClient
|
||||
if (NotificationDispatcher is null)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"PluginIpcClient is not yet bound to a dotnetCampus.Ipc transport dispatcher. " +
|
||||
"AirAppIpcClient is not yet bound to a dotnetCampus.Ipc transport dispatcher. " +
|
||||
"Wire NotificationDispatcher during host/worker transport integration.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.AirAppIsolation.Ipc;
|
||||
|
||||
public sealed record AirAppIpcClientOptions
|
||||
{
|
||||
public required string PipeName { get; init; }
|
||||
|
||||
public string ProtocolVersion { get; init; } = AirAppIsolationProtocolVersion.Current;
|
||||
|
||||
public TimeSpan ConnectTimeout { get; init; } = AirAppIpcConstants.DefaultConnectTimeout;
|
||||
|
||||
public TimeSpan RequestTimeout { get; init; } = AirAppIpcConstants.DefaultRequestTimeout;
|
||||
|
||||
public JsonSerializerContext SerializerContext { get; init; } = AirAppIsolationJsonContext.Default;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
using LanMountainDesktop.PluginIsolation.Contracts;
|
||||
using LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.PluginIsolation.Ipc;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Ipc;
|
||||
|
||||
public static class PluginIpcConstants
|
||||
public static class AirAppIpcConstants
|
||||
{
|
||||
public const string EnvironmentPluginId = "LANMOUNTAIN_PLUGIN_ID";
|
||||
public const string EnvironmentAirAppId = "LANMOUNTAIN_PLUGIN_ID";
|
||||
public const string EnvironmentSessionId = "LANMOUNTAIN_PLUGIN_SESSION_ID";
|
||||
public const string EnvironmentHostPipeName = "LANMOUNTAIN_PLUGIN_HOST_PIPE";
|
||||
public const string EnvironmentProtocolVersion = "LANMOUNTAIN_PLUGIN_PROTOCOL_VERSION";
|
||||
public const string EnvironmentRuntimeMode = "LANMOUNTAIN_PLUGIN_RUNTIME_MODE";
|
||||
|
||||
public const string CommandLinePluginId = "--plugin-id";
|
||||
public const string CommandLineAirAppId = "--plugin-id";
|
||||
public const string CommandLineSessionId = "--session-id";
|
||||
public const string CommandLineHostPipeName = "--host-pipe-name";
|
||||
public const string CommandLineProtocolVersion = "--protocol-version";
|
||||
@@ -21,5 +21,5 @@ public static class PluginIpcConstants
|
||||
public static readonly TimeSpan DefaultHeartbeatInterval = TimeSpan.FromSeconds(5);
|
||||
public static readonly TimeSpan DefaultHeartbeatTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
public const string DefaultProtocolVersion = PluginIsolationProtocolVersion.Current;
|
||||
public const string DefaultProtocolVersion = AirAppIsolationProtocolVersion.Current;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LanMountainDesktop.PluginIsolation.Ipc;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Ipc;
|
||||
|
||||
public delegate Task<JsonElement?> PluginIpcRequestDispatcher(
|
||||
public delegate Task<JsonElement?> AirAppIpcRequestDispatcher(
|
||||
string route,
|
||||
JsonElement? payload,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
public delegate Task PluginIpcNotificationDispatcher(
|
||||
public delegate Task AirAppIpcNotificationDispatcher(
|
||||
string route,
|
||||
JsonElement? payload,
|
||||
CancellationToken cancellationToken);
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginIsolation.Contracts;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
public static class PluginIpcErrorCodes
|
||||
public static class AirAppIpcErrorCodes
|
||||
{
|
||||
public const string ProtocolMismatch = "protocol_mismatch";
|
||||
public const string SessionRejected = "session_rejected";
|
||||
@@ -0,0 +1,17 @@
|
||||
using LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.AirAppIsolation.Ipc;
|
||||
|
||||
public static class AirAppIpcRoutedNotifyIds
|
||||
{
|
||||
public const string SessionReady = AirAppIpcRoutes.Session.Ready;
|
||||
public const string LifecycleStateChanged = AirAppIpcRoutes.Lifecycle.StateChanged;
|
||||
public const string SettingsChanged = AirAppIpcRoutes.Settings.Changed;
|
||||
public const string AppearanceChanged = AirAppIpcRoutes.Appearance.Changed;
|
||||
public const string UiDetach = AirAppIpcRoutes.Ui.Detach;
|
||||
public const string UiStateChanged = AirAppIpcRoutes.Ui.StateChanged;
|
||||
public const string HeartbeatPing = AirAppIpcRoutes.Heartbeat.Ping;
|
||||
public const string HeartbeatPong = AirAppIpcRoutes.Heartbeat.Pong;
|
||||
public const string LogWrite = AirAppIpcRoutes.Log.Write;
|
||||
public const string FaultReport = AirAppIpcRoutes.Fault.Report;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginIsolation.Contracts;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
public static class PluginIpcRoutes
|
||||
public static class AirAppIpcRoutes
|
||||
{
|
||||
public static class Session
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LanMountainDesktop.PluginIsolation.Ipc;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Ipc;
|
||||
|
||||
public sealed class PluginIpcServer
|
||||
public sealed class AirAppIpcServer
|
||||
{
|
||||
private readonly Dictionary<string, Func<JsonElement?, CancellationToken, Task<JsonElement?>>> _requestHandlers =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -11,14 +11,14 @@ public sealed class PluginIpcServer
|
||||
private readonly Dictionary<string, Func<JsonElement?, CancellationToken, Task>> _notificationHandlers =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public PluginIpcServer(PluginIpcServerOptions options)
|
||||
public AirAppIpcServer(AirAppIpcServerOptions options)
|
||||
{
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
SerializerContext = options.SerializerContext ?? throw new ArgumentNullException(nameof(options.SerializerContext));
|
||||
SerializerOptions = SerializerContext.Options;
|
||||
}
|
||||
|
||||
public PluginIpcServerOptions Options { get; }
|
||||
public AirAppIpcServerOptions Options { get; }
|
||||
|
||||
public JsonSerializerContext SerializerContext { get; }
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.AirAppIsolation.Ipc;
|
||||
|
||||
public sealed record AirAppIpcServerOptions
|
||||
{
|
||||
public required string PipeName { get; init; }
|
||||
|
||||
public string ProtocolVersion { get; init; } = AirAppIsolationProtocolVersion.Current;
|
||||
|
||||
public TimeSpan HeartbeatInterval { get; init; } = AirAppIpcConstants.DefaultHeartbeatInterval;
|
||||
|
||||
public TimeSpan HeartbeatTimeout { get; init; } = AirAppIpcConstants.DefaultHeartbeatTimeout;
|
||||
|
||||
public JsonSerializerContext SerializerContext { get; init; } = AirAppIsolationJsonContext.Default;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
[JsonSourceGenerationOptions(
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(AirAppCapabilityDeclaration))]
|
||||
[JsonSerializable(typeof(List<AirAppCapabilityDeclaration>))]
|
||||
[JsonSerializable(typeof(AirAppSessionHandshakeRequest))]
|
||||
[JsonSerializable(typeof(AirAppSessionHandshakeResponse))]
|
||||
[JsonSerializable(typeof(AirAppReadyNotification))]
|
||||
[JsonSerializable(typeof(AirAppInitializeRequest))]
|
||||
[JsonSerializable(typeof(AirAppInitializeResponse))]
|
||||
[JsonSerializable(typeof(AirAppStopRequest))]
|
||||
[JsonSerializable(typeof(AirAppRestartRequest))]
|
||||
[JsonSerializable(typeof(AirAppLifecycleStateChanged))]
|
||||
[JsonSerializable(typeof(AirAppSettingsSnapshotRequest))]
|
||||
[JsonSerializable(typeof(AirAppSettingsSnapshotResponse))]
|
||||
[JsonSerializable(typeof(AirAppSettingsWriteRequest))]
|
||||
[JsonSerializable(typeof(AirAppSettingsWriteResponse))]
|
||||
[JsonSerializable(typeof(AirAppSettingsChangedNotification))]
|
||||
[JsonSerializable(typeof(AirAppAppearanceSnapshotRequest))]
|
||||
[JsonSerializable(typeof(AirAppMaterialSurfaceSnapshot))]
|
||||
[JsonSerializable(typeof(AirAppAppearanceSnapshot))]
|
||||
[JsonSerializable(typeof(AirAppAppearanceChangedNotification))]
|
||||
[JsonSerializable(typeof(AirAppUiSurfaceDescriptor))]
|
||||
[JsonSerializable(typeof(List<AirAppUiSurfaceDescriptor>))]
|
||||
[JsonSerializable(typeof(AirAppUiAttachRequest))]
|
||||
[JsonSerializable(typeof(AirAppUiAttachResponse))]
|
||||
[JsonSerializable(typeof(AirAppUiDetachNotification))]
|
||||
[JsonSerializable(typeof(AirAppUiCommandRequest))]
|
||||
[JsonSerializable(typeof(AirAppUiCommandResponse))]
|
||||
[JsonSerializable(typeof(AirAppUiStateChangedNotification))]
|
||||
[JsonSerializable(typeof(AirAppHeartbeatPing))]
|
||||
[JsonSerializable(typeof(AirAppHeartbeatPong))]
|
||||
[JsonSerializable(typeof(AirAppLogEntry))]
|
||||
[JsonSerializable(typeof(AirAppFaultReport))]
|
||||
public partial class AirAppIsolationJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
public static class AirAppIsolationProtocolVersion
|
||||
{
|
||||
public const string Current = "1.0";
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginLocalizer
|
||||
public sealed class AirAppLocalizer
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -14,28 +14,28 @@ public sealed class PluginLocalizer
|
||||
private readonly Dictionary<string, Dictionary<string, string>> _cache =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public PluginLocalizer(string pluginDirectory, string? languageCode)
|
||||
public AirAppLocalizer(string pluginDirectory, string? languageCode)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(pluginDirectory);
|
||||
|
||||
PluginDirectory = pluginDirectory;
|
||||
AirAppDirectory = pluginDirectory;
|
||||
LanguageCode = NormalizeLanguageCode(languageCode);
|
||||
}
|
||||
|
||||
public string PluginDirectory { get; }
|
||||
public string AirAppDirectory { get; }
|
||||
|
||||
public string LanguageCode { get; }
|
||||
|
||||
public static PluginLocalizer Create(IPluginRuntimeContext context)
|
||||
public static AirAppLocalizer Create(IAirAppRuntimeContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return new PluginLocalizer(context.PluginDirectory, ResolveLanguageCode(context.Properties));
|
||||
return new AirAppLocalizer(context.AirAppDirectory, ResolveLanguageCode(context.Properties));
|
||||
}
|
||||
|
||||
public static PluginLocalizer Create(PluginDesktopComponentContext context)
|
||||
public static AirAppLocalizer Create(AirAppComponentContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
return new PluginLocalizer(context.PluginDirectory, ResolveLanguageCode(context.Properties));
|
||||
return new AirAppLocalizer(context.AirAppDirectory, ResolveLanguageCode(context.Properties));
|
||||
}
|
||||
|
||||
public string GetString(string key, string fallback)
|
||||
@@ -76,7 +76,7 @@ public sealed class PluginLocalizer
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(properties);
|
||||
|
||||
return properties.TryGetValue(PluginHostPropertyKeys.HostLanguageCode, out var rawValue) &&
|
||||
return properties.TryGetValue(AirAppHostPropertyKeys.HostLanguageCode, out var rawValue) &&
|
||||
rawValue is string languageCode
|
||||
? NormalizeLanguageCode(languageCode)
|
||||
: NormalizeLanguageCode(CultureInfo.CurrentUICulture.Name);
|
||||
@@ -92,7 +92,7 @@ public sealed class PluginLocalizer
|
||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
try
|
||||
{
|
||||
var filePath = Path.Combine(PluginDirectory, "Localization", $"{languageCode}.json");
|
||||
var filePath = Path.Combine(AirAppDirectory, "Localization", $"{languageCode}.json");
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var json = File.ReadAllText(filePath).TrimStart('\uFEFF');
|
||||
@@ -2,9 +2,6 @@ using System.Text.Json;
|
||||
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// AirApp manifest (airapp.json).
|
||||
/// </summary>
|
||||
public sealed record AirAppManifest(
|
||||
string Id,
|
||||
string Name,
|
||||
@@ -13,11 +10,11 @@ public sealed record AirAppManifest(
|
||||
string? Author = null,
|
||||
string? Version = null,
|
||||
string? ApiVersion = null,
|
||||
IReadOnlyList<AirAppSharedContractReference>? SharedContracts = null,
|
||||
AirAppRuntimeConfiguration? Runtime = null,
|
||||
IReadOnlyList<AirAppComponentManifest>? Components = null,
|
||||
IReadOnlyList<AirAppWindowManifest>? Windows = null,
|
||||
IReadOnlyList<string>? Permissions = null,
|
||||
IReadOnlyList<AirAppSharedContractReference>? SharedContracts = null)
|
||||
IReadOnlyList<string>? Permissions = null)
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
@@ -26,9 +23,6 @@ public sealed record AirAppManifest(
|
||||
AllowTrailingCommas = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Load manifest from file.
|
||||
/// </summary>
|
||||
public static AirAppManifest Load(string manifestPath)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(manifestPath);
|
||||
@@ -37,9 +31,6 @@ public sealed record AirAppManifest(
|
||||
return Load(stream, manifestPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load manifest from stream.
|
||||
/// </summary>
|
||||
public static AirAppManifest Load(Stream stream, string sourceName)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
@@ -48,15 +39,12 @@ public sealed record AirAppManifest(
|
||||
var manifest = JsonSerializer.Deserialize<AirAppManifest>(stream, SerializerOptions);
|
||||
if (manifest is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to deserialize AirApp manifest '{sourceName}'.");
|
||||
throw new InvalidOperationException($"Failed to deserialize plugin manifest '{sourceName}'.");
|
||||
}
|
||||
|
||||
return manifest.NormalizeAndValidate(sourceName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve entrance assembly path.
|
||||
/// </summary>
|
||||
public string ResolveEntranceAssemblyPath(string manifestPath)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(manifestPath);
|
||||
@@ -67,21 +55,18 @@ public sealed record AirAppManifest(
|
||||
}
|
||||
|
||||
var manifestDirectory = Path.GetDirectoryName(Path.GetFullPath(manifestPath))
|
||||
?? throw new InvalidOperationException($"Failed to determine directory of '{manifestPath}'.");
|
||||
?? throw new InvalidOperationException($"Failed to determine the directory of '{manifestPath}'.");
|
||||
|
||||
return Path.GetFullPath(Path.Combine(manifestDirectory, EntranceAssembly));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get runtime mode.
|
||||
/// </summary>
|
||||
public AirAppRuntimeMode RuntimeMode =>
|
||||
AirAppRuntimeModes.TryParse(Runtime?.Mode, out var mode) ? mode : AirAppRuntimeMode.InProcess;
|
||||
|
||||
private AirAppManifest NormalizeAndValidate(string manifestPath)
|
||||
{
|
||||
var normalizedSharedContracts = NormalizeSharedContracts(manifestPath, SharedContracts);
|
||||
var normalizedRuntime = (Runtime ?? new AirAppRuntimeConfiguration()).NormalizeAndValidate(manifestPath);
|
||||
|
||||
var normalized = this with
|
||||
{
|
||||
Id = RequireValue(Id, nameof(Id), manifestPath),
|
||||
@@ -91,18 +76,17 @@ public sealed record AirAppManifest(
|
||||
Author = NormalizeOptionalValue(Author),
|
||||
Version = NormalizeOptionalValue(Version),
|
||||
ApiVersion = NormalizeOptionalValue(ApiVersion) ?? AirAppSdkInfo.ApiVersion,
|
||||
SharedContracts = normalizedSharedContracts,
|
||||
Runtime = normalizedRuntime,
|
||||
Components = Components ?? Array.Empty<AirAppComponentManifest>(),
|
||||
Windows = Windows ?? Array.Empty<AirAppWindowManifest>(),
|
||||
Permissions = Permissions ?? Array.Empty<string>(),
|
||||
SharedContracts = SharedContracts ?? Array.Empty<AirAppSharedContractReference>()
|
||||
Permissions = Permissions ?? Array.Empty<string>()
|
||||
};
|
||||
|
||||
// Validate API version
|
||||
if (!System.Version.TryParse(normalized.ApiVersion, out var requestedVersion))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp manifest '{manifestPath}' declares invalid API version '{normalized.ApiVersion}'.");
|
||||
$"AirApp manifest '{manifestPath}' declares an invalid API version '{normalized.ApiVersion}'.");
|
||||
}
|
||||
|
||||
if (!System.Version.TryParse(AirAppSdkInfo.ApiVersion, out var currentVersion))
|
||||
@@ -113,10 +97,43 @@ public sealed record AirAppManifest(
|
||||
if (requestedVersion.Major != currentVersion.Major)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp '{normalized.Id}' targets API version '{normalized.ApiVersion}' (major {requestedVersion.Major}), " +
|
||||
$"but the host provides '{AirAppSdkInfo.ApiVersion}' (major {currentVersion.Major}). " +
|
||||
$"This host only supports v{currentVersion.Major}.x AirApps and rejects v{requestedVersion.Major}.x packages. " +
|
||||
$"Migrate the AirApp manifest and code to API {AirAppSdkInfo.ApiVersion}, then rebuild and republish.");
|
||||
$"AirApp '{normalized.Id}' targets API version '{normalized.ApiVersion}', " +
|
||||
$"but the host provides '{AirAppSdkInfo.ApiVersion}'. " +
|
||||
$"This host only supports API {AirAppSdkInfo.ApiVersion} plugins.");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AirAppSharedContractReference> NormalizeSharedContracts(
|
||||
string manifestPath,
|
||||
IReadOnlyList<AirAppSharedContractReference>? sharedContracts)
|
||||
{
|
||||
if (sharedContracts is null || sharedContracts.Count == 0)
|
||||
{
|
||||
return Array.Empty<AirAppSharedContractReference>();
|
||||
}
|
||||
|
||||
var normalized = new List<AirAppSharedContractReference>(sharedContracts.Count);
|
||||
var seenIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var contract in sharedContracts)
|
||||
{
|
||||
if (contract is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp manifest '{manifestPath}' contains a null shared contract declaration.");
|
||||
}
|
||||
|
||||
var normalizedContract = contract.NormalizeAndValidate(manifestPath);
|
||||
var contractKey = $"{normalizedContract.Id}@{normalizedContract.Version}";
|
||||
if (!seenIds.Add(contractKey))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AirApp manifest '{manifestPath}' declares duplicate shared contract '{contractKey}'.");
|
||||
}
|
||||
|
||||
normalized.Add(normalizedContract);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
@@ -141,7 +158,7 @@ public sealed record AirAppManifest(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Component declaration in manifest.
|
||||
/// Component declaration in the AirApp manifest.
|
||||
/// </summary>
|
||||
public sealed record AirAppComponentManifest(
|
||||
string Id,
|
||||
@@ -153,7 +170,7 @@ public sealed record AirAppComponentManifest(
|
||||
string? IconKey = null);
|
||||
|
||||
/// <summary>
|
||||
/// Window declaration in manifest.
|
||||
/// Window declaration in the AirApp manifest.
|
||||
/// </summary>
|
||||
public sealed record AirAppWindowManifest(
|
||||
string Id,
|
||||
@@ -161,28 +178,3 @@ public sealed record AirAppWindowManifest(
|
||||
double DefaultWidth = 800,
|
||||
double DefaultHeight = 600,
|
||||
string? Description = null);
|
||||
|
||||
/// <summary>
|
||||
/// Shared contract reference.
|
||||
/// </summary>
|
||||
public sealed record AirAppSharedContractReference(
|
||||
string Id,
|
||||
string Version);
|
||||
|
||||
/// <summary>
|
||||
/// Runtime configuration.
|
||||
/// </summary>
|
||||
public sealed record AirAppRuntimeConfiguration
|
||||
{
|
||||
public string? Mode { get; init; }
|
||||
public IReadOnlyList<string>? Capabilities { get; init; }
|
||||
|
||||
internal AirAppRuntimeConfiguration NormalizeAndValidate(string manifestPath)
|
||||
{
|
||||
return this with
|
||||
{
|
||||
Mode = string.IsNullOrWhiteSpace(Mode) ? "in-process" : Mode.Trim().ToLowerInvariant(),
|
||||
Capabilities = Capabilities ?? Array.Empty<string>()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record AirAppPackageInstallResult(
|
||||
AirAppManifest Manifest,
|
||||
bool ReplacedExisting,
|
||||
bool RestartRequired);
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record PluginPublicIpcServiceDescriptor(
|
||||
public sealed record AirAppPublicIpcServiceDescriptor(
|
||||
Type ContractType,
|
||||
object Implementation,
|
||||
string? ObjectId,
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record AirAppPublicIpcServiceRegistration(
|
||||
Type ContractType,
|
||||
string? ObjectId,
|
||||
string[] NotifyIds);
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record AirAppRuntimeConfiguration(string Mode = AirAppRuntimeModes.InProcess)
|
||||
{
|
||||
public AirAppRuntimeMode RuntimeMode =>
|
||||
AirAppRuntimeModes.TryParse(Mode, out var mode) ? mode : AirAppRuntimeMode.InProcess;
|
||||
|
||||
internal AirAppRuntimeConfiguration NormalizeAndValidate(string manifestPath)
|
||||
{
|
||||
return this with
|
||||
{
|
||||
Mode = AirAppRuntimeModes.NormalizeManifestValue(Mode, manifestPath)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public enum PluginRuntimeMode
|
||||
public enum AirAppRuntimeMode
|
||||
{
|
||||
InProcess = 0,
|
||||
IsolatedBackground = 1,
|
||||
@@ -1,25 +1,27 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class PluginRuntimeModes
|
||||
public static class AirAppRuntimeModes
|
||||
{
|
||||
public const string InProcess = "in-proc";
|
||||
public const string InProcessAlt = "in-process";
|
||||
public const string IsolatedBackground = "isolated-background";
|
||||
public const string IsolatedWindow = "isolated-window";
|
||||
|
||||
public static bool TryParse(string? value, out PluginRuntimeMode mode)
|
||||
public static bool TryParse(string? value, out AirAppRuntimeMode mode)
|
||||
{
|
||||
switch (value?.Trim().ToLowerInvariant())
|
||||
{
|
||||
case null:
|
||||
case "":
|
||||
case InProcess:
|
||||
mode = PluginRuntimeMode.InProcess;
|
||||
case InProcessAlt:
|
||||
mode = AirAppRuntimeMode.InProcess;
|
||||
return true;
|
||||
case IsolatedBackground:
|
||||
mode = PluginRuntimeMode.IsolatedBackground;
|
||||
mode = AirAppRuntimeMode.IsolatedBackground;
|
||||
return true;
|
||||
case IsolatedWindow:
|
||||
mode = PluginRuntimeMode.IsolatedWindow;
|
||||
mode = AirAppRuntimeMode.IsolatedWindow;
|
||||
return true;
|
||||
default:
|
||||
mode = default;
|
||||
@@ -27,7 +29,7 @@ public static class PluginRuntimeModes
|
||||
}
|
||||
}
|
||||
|
||||
public static PluginRuntimeMode Parse(string? value, string sourceName, string propertyName = "runtime.mode")
|
||||
public static AirAppRuntimeMode Parse(string? value, string sourceName, string propertyName = "runtime.mode")
|
||||
{
|
||||
if (TryParse(value, out var mode))
|
||||
{
|
||||
@@ -36,7 +38,7 @@ public static class PluginRuntimeModes
|
||||
|
||||
var candidate = string.IsNullOrWhiteSpace(value) ? "<empty>" : value.Trim();
|
||||
throw new InvalidOperationException(
|
||||
$"Plugin manifest '{sourceName}' declares unsupported runtime mode '{candidate}' in '{propertyName}'. " +
|
||||
$"AirApp manifest '{sourceName}' declares unsupported runtime mode '{candidate}' in '{propertyName}'. " +
|
||||
$"Supported values: '{InProcess}', '{IsolatedBackground}', '{IsolatedWindow}'.");
|
||||
}
|
||||
|
||||
@@ -45,13 +47,13 @@ public static class PluginRuntimeModes
|
||||
return ToManifestValue(Parse(value, sourceName, propertyName));
|
||||
}
|
||||
|
||||
public static string ToManifestValue(PluginRuntimeMode mode)
|
||||
public static string ToManifestValue(AirAppRuntimeMode mode)
|
||||
{
|
||||
return mode switch
|
||||
{
|
||||
PluginRuntimeMode.InProcess => InProcess,
|
||||
PluginRuntimeMode.IsolatedBackground => IsolatedBackground,
|
||||
PluginRuntimeMode.IsolatedWindow => IsolatedWindow,
|
||||
AirAppRuntimeMode.InProcess => InProcess,
|
||||
AirAppRuntimeMode.IsolatedBackground => IsolatedBackground,
|
||||
AirAppRuntimeMode.IsolatedWindow => IsolatedWindow,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported plugin runtime mode.")
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class PluginSdkInfo
|
||||
public static class AirAppSdkInfo
|
||||
{
|
||||
public const string ApiVersion = "5.0.0";
|
||||
public const string ManifestFileName = "plugin.json";
|
||||
public const string SdkVersion = "1.0.0";
|
||||
public const string ApiVersion = "1.0.0";
|
||||
public const string ManifestFileName = "airapp.json";
|
||||
public const string PackageFileExtension = ".laapp";
|
||||
public const string DataDirectoryName = "Data";
|
||||
public const string RuntimeDirectoryName = ".runtime";
|
||||
@@ -2,15 +2,15 @@ using Avalonia.Controls;
|
||||
using dotnetCampus.Ipc.CompilerServices.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class PluginServiceCollectionExtensions
|
||||
public static class AirAppServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddPluginSettingsSection(
|
||||
public static IServiceCollection AddAirAppSettingsSection(
|
||||
this IServiceCollection services,
|
||||
string id,
|
||||
string titleLocalizationKey,
|
||||
Action<PluginSettingsSectionBuilder> configure,
|
||||
Action<AirAppSettingsSectionBuilder> configure,
|
||||
string? descriptionLocalizationKey = null,
|
||||
string iconKey = "PuzzlePiece",
|
||||
int sortOrder = 0)
|
||||
@@ -18,7 +18,7 @@ public static class PluginServiceCollectionExtensions
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
|
||||
var builder = new PluginSettingsSectionBuilder(
|
||||
var builder = new AirAppSettingsSectionBuilder(
|
||||
id,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
@@ -35,19 +35,19 @@ public static class PluginServiceCollectionExtensions
|
||||
/// in the settings window, allowing the plugin to use any Fluent Avalonia controls
|
||||
/// and custom layouts — just like built-in settings pages.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView">A <see cref="SettingsPageBase"/> subclass that defines the settings UI using AXAML.</typeparam>
|
||||
public static IServiceCollection AddPluginSettingsSection<TView>(
|
||||
/// <typeparam name="TView">A <see cref="AirAppSettingsPageBase"/> subclass that defines the settings UI using AXAML.</typeparam>
|
||||
public static IServiceCollection AddAirAppSettingsSection<TView>(
|
||||
this IServiceCollection services,
|
||||
string id,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
string iconKey = "PuzzlePiece",
|
||||
int sortOrder = 0)
|
||||
where TView : SettingsPageBase
|
||||
where TView : AirAppSettingsPageBase
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
var builder = new PluginSettingsSectionBuilder(
|
||||
var builder = new AirAppSettingsSectionBuilder(
|
||||
id,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
@@ -58,21 +58,21 @@ public static class PluginServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddPluginDesktopComponent<TControl>(
|
||||
public static IServiceCollection AddAirAppComponent<TControl>(
|
||||
this IServiceCollection services,
|
||||
PluginDesktopComponentOptions options)
|
||||
AirAppComponentOptions options)
|
||||
where TControl : Control
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
services.AddSingleton(new PluginDesktopComponentRegistration(
|
||||
services.AddSingleton(new AirAppComponentRegistration(
|
||||
(provider, context) => ActivatorUtilities.CreateInstance<TControl>(provider, context),
|
||||
options));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddPluginDesktopComponentEditor<TControl>(
|
||||
public static IServiceCollection AddAirAppComponentEditor<TControl>(
|
||||
this IServiceCollection services,
|
||||
string componentId,
|
||||
double preferredWidth = 720d,
|
||||
@@ -83,7 +83,7 @@ public static class PluginServiceCollectionExtensions
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddSingleton(new PluginDesktopComponentEditorRegistration(
|
||||
services.AddSingleton(new AirAppComponentEditorRegistration(
|
||||
componentId,
|
||||
(provider, context) => ActivatorUtilities.CreateInstance<TControl>(provider, context),
|
||||
preferredWidth,
|
||||
@@ -93,7 +93,28 @@ public static class PluginServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddPluginExport<TContract, TImplementation>(this IServiceCollection services)
|
||||
/// <summary>
|
||||
/// Registers a window AirApp entry.
|
||||
/// The host resolves the window by <paramref name="id"/> and hosts its
|
||||
/// <see cref="IAirAppWindow"/> content inside the AirAppHost window shell.
|
||||
/// </summary>
|
||||
/// <typeparam name="TWindow">A class implementing <see cref="IAirAppWindow"/>.</typeparam>
|
||||
/// <param name="id">Unique window identifier.</param>
|
||||
/// <param name="name">Display name.</param>
|
||||
public static IServiceCollection AddAirAppWindow<TWindow>(
|
||||
this IServiceCollection services,
|
||||
string id,
|
||||
string name)
|
||||
where TWindow : class, IAirAppWindow
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddTransient<TWindow>();
|
||||
services.AddSingleton(new AirAppWindowRegistration(id, name, typeof(TWindow)));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAirAppExport<TContract, TImplementation>(this IServiceCollection services)
|
||||
where TContract : class
|
||||
where TImplementation : class, TContract
|
||||
{
|
||||
@@ -102,18 +123,18 @@ public static class PluginServiceCollectionExtensions
|
||||
EnsureSingletonRegistration<TContract, TImplementation>(services);
|
||||
|
||||
if (!services.Any(descriptor =>
|
||||
descriptor.ServiceType == typeof(PluginServiceExportRegistration) &&
|
||||
descriptor.ImplementationInstance is PluginServiceExportRegistration existing &&
|
||||
descriptor.ServiceType == typeof(AirAppServiceExportRegistration) &&
|
||||
descriptor.ImplementationInstance is AirAppServiceExportRegistration existing &&
|
||||
existing.ContractType == typeof(TContract) &&
|
||||
existing.ImplementationType == typeof(TImplementation)))
|
||||
{
|
||||
services.AddSingleton(new PluginServiceExportRegistration(typeof(TContract), typeof(TImplementation)));
|
||||
services.AddSingleton(new AirAppServiceExportRegistration(typeof(TContract), typeof(TImplementation)));
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddPluginPublicIpc<TContract, TImplementation>(
|
||||
public static IServiceCollection AddAirAppPublicIpc<TContract, TImplementation>(
|
||||
this IServiceCollection services,
|
||||
string? objectId = null,
|
||||
params string[] notifyIds)
|
||||
@@ -125,12 +146,12 @@ public static class PluginServiceCollectionExtensions
|
||||
EnsureSingletonRegistration<TContract, TImplementation>(services);
|
||||
|
||||
if (!services.Any(descriptor =>
|
||||
descriptor.ServiceType == typeof(PluginPublicIpcServiceRegistration) &&
|
||||
descriptor.ImplementationInstance is PluginPublicIpcServiceRegistration existing &&
|
||||
descriptor.ServiceType == typeof(AirAppPublicIpcServiceRegistration) &&
|
||||
descriptor.ImplementationInstance is AirAppPublicIpcServiceRegistration existing &&
|
||||
existing.ContractType == typeof(TContract) &&
|
||||
string.Equals(existing.ObjectId, objectId, StringComparison.Ordinal)))
|
||||
{
|
||||
services.AddSingleton(new PluginPublicIpcServiceRegistration(
|
||||
services.AddSingleton(new AirAppPublicIpcServiceRegistration(
|
||||
typeof(TContract),
|
||||
objectId,
|
||||
notifyIds ?? []));
|
||||
@@ -139,11 +160,11 @@ public static class PluginServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddPluginPublicIpcContributor<TContributor>(this IServiceCollection services)
|
||||
where TContributor : class, IPluginPublicIpcContributor
|
||||
public static IServiceCollection AddAirAppPublicIpcContributor<TContributor>(this IServiceCollection services)
|
||||
where TContributor : class, IAirAppPublicIpcContributor
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
services.AddSingleton<IPluginPublicIpcContributor, TContributor>();
|
||||
services.AddSingleton<IAirAppPublicIpcContributor, TContributor>();
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record AirAppServiceExportDescriptor(
|
||||
string ProviderAirAppId,
|
||||
Type ContractType,
|
||||
object ServiceInstance);
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginServiceExportRegistration
|
||||
public sealed class AirAppServiceExportRegistration
|
||||
{
|
||||
public PluginServiceExportRegistration(Type contractType, Type implementationType)
|
||||
public AirAppServiceExportRegistration(Type contractType, Type implementationType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(contractType);
|
||||
ArgumentNullException.ThrowIfNull(implementationType);
|
||||
@@ -1,8 +1,8 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class SettingsOptionChoice
|
||||
public sealed class AirAppSettingOptionChoice
|
||||
{
|
||||
public SettingsOptionChoice(string value, string titleLocalizationKey)
|
||||
public AirAppSettingOptionChoice(string value, string titleLocalizationKey)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(value);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(titleLocalizationKey);
|
||||
@@ -1,16 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class SettingsOptionDefinition
|
||||
public sealed class AirAppSettingOptionDefinition
|
||||
{
|
||||
public SettingsOptionDefinition(
|
||||
public AirAppSettingOptionDefinition(
|
||||
string key,
|
||||
SettingsOptionType optionType,
|
||||
AirAppSettingOptionType optionType,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
object? defaultValue = null,
|
||||
IReadOnlyList<SettingsOptionChoice>? choices = null,
|
||||
IReadOnlyList<AirAppSettingOptionChoice>? choices = null,
|
||||
double? minimum = null,
|
||||
double? maximum = null,
|
||||
string? validationPattern = null)
|
||||
@@ -35,7 +35,7 @@ public sealed class SettingsOptionDefinition
|
||||
|
||||
public string Key { get; }
|
||||
|
||||
public SettingsOptionType OptionType { get; }
|
||||
public AirAppSettingOptionType OptionType { get; }
|
||||
|
||||
public string TitleLocalizationKey { get; }
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class SettingsOptionDefinition
|
||||
|
||||
public object? DefaultValue { get; }
|
||||
|
||||
public IReadOnlyList<SettingsOptionChoice> Choices { get; }
|
||||
public IReadOnlyList<AirAppSettingOptionChoice> Choices { get; }
|
||||
|
||||
public double? Minimum { get; }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public enum SettingsOptionType
|
||||
public enum AirAppSettingOptionType
|
||||
{
|
||||
Toggle = 0,
|
||||
Select = 1,
|
||||
@@ -1,14 +1,14 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class SettingsCategories
|
||||
public static class AirAppSettingsCategories
|
||||
{
|
||||
public const string General = "General";
|
||||
public const string Appearance = "Appearance";
|
||||
public const string Components = "Components";
|
||||
public const string Plugins = "Plugins";
|
||||
public const string PluginCatalog = "PluginCatalog";
|
||||
[Obsolete("Use PluginCatalog instead.")]
|
||||
public const string PluginMarket = PluginCatalog;
|
||||
public const string AirApps = "AirApps";
|
||||
public const string AirAppCatalog = "AirAppCatalog";
|
||||
[Obsolete("Use AirAppCatalog instead.")]
|
||||
public const string AirAppMarket = AirAppCatalog;
|
||||
public const string Update = "Update";
|
||||
public const string About = "About";
|
||||
public const string Advanced = "Advanced";
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public abstract class SettingsPageBase : UserControl
|
||||
public abstract class AirAppSettingsPageBase : UserControl
|
||||
{
|
||||
public static readonly string DialogHostIdentifier = "LanMountainDesktop.SettingsWindow";
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public enum AirAppSettingsPageCategory
|
||||
{
|
||||
General = 0,
|
||||
Appearance = 10,
|
||||
Components = 20,
|
||||
AirApps = 30,
|
||||
AirAppCatalog = 35,
|
||||
[Obsolete("Use AirAppCatalog instead.")]
|
||||
AirAppMarket = 35,
|
||||
About = 40,
|
||||
Dev = 50
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class SettingsPageInfoAttribute : Attribute
|
||||
public sealed class AirAppSettingsPageInfoAttribute : Attribute
|
||||
{
|
||||
public SettingsPageInfoAttribute(
|
||||
public AirAppSettingsPageInfoAttribute(
|
||||
string id,
|
||||
string name,
|
||||
SettingsPageCategory category)
|
||||
AirAppSettingsPageCategory category)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(id);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(name);
|
||||
@@ -22,7 +22,7 @@ public sealed class SettingsPageInfoAttribute : Attribute
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public SettingsPageCategory Category { get; }
|
||||
public AirAppSettingsPageCategory Category { get; }
|
||||
|
||||
public string? TitleLocalizationKey { get; init; }
|
||||
|
||||
@@ -42,5 +42,5 @@ public sealed class SettingsPageInfoAttribute : Attribute
|
||||
|
||||
public string? GroupId { get; init; }
|
||||
|
||||
public SettingsScope Scope { get; init; } = SettingsScope.App;
|
||||
public AirAppSettingsScope Scope { get; init; } = AirAppSettingsScope.App;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public enum AirAppSettingsScope
|
||||
{
|
||||
App = 0,
|
||||
Launcher = 1,
|
||||
AirApp = 2,
|
||||
ComponentInstance = 3
|
||||
}
|
||||
@@ -2,14 +2,14 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginSettingsSectionBuilder
|
||||
public sealed class AirAppSettingsSectionBuilder
|
||||
{
|
||||
private readonly List<SettingsOptionDefinition> _options = [];
|
||||
private readonly List<AirAppSettingOptionDefinition> _options = [];
|
||||
private Type? _customViewType;
|
||||
|
||||
internal PluginSettingsSectionBuilder(
|
||||
internal AirAppSettingsSectionBuilder(
|
||||
string id,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey,
|
||||
@@ -35,17 +35,17 @@ public sealed class PluginSettingsSectionBuilder
|
||||
|
||||
public Type? CustomViewType => _customViewType;
|
||||
|
||||
public IReadOnlyList<SettingsOptionDefinition> Options => _options;
|
||||
public IReadOnlyList<AirAppSettingOptionDefinition> Options => _options;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom AXAML view for this settings section.
|
||||
/// The view type must be a subclass of <see cref="SettingsPageBase"/>.
|
||||
/// The view type must be a subclass of <see cref="AirAppSettingsPageBase"/>.
|
||||
/// When a custom view is provided, the host application will use it directly
|
||||
/// instead of generating a page from the declared options, allowing the plugin
|
||||
/// to use any Fluent Avalonia controls and custom layouts.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView">A <see cref="SettingsPageBase"/> subclass that defines the settings UI.</typeparam>
|
||||
public PluginSettingsSectionBuilder SetCustomView<TView>() where TView : SettingsPageBase
|
||||
/// <typeparam name="TView">A <see cref="AirAppSettingsPageBase"/> subclass that defines the settings UI.</typeparam>
|
||||
public AirAppSettingsSectionBuilder SetCustomView<TView>() where TView : AirAppSettingsPageBase
|
||||
{
|
||||
_customViewType = typeof(TView);
|
||||
return this;
|
||||
@@ -53,19 +53,19 @@ public sealed class PluginSettingsSectionBuilder
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom AXAML view for this settings section.
|
||||
/// The view type must be a subclass of <see cref="SettingsPageBase"/>.
|
||||
/// The view type must be a subclass of <see cref="AirAppSettingsPageBase"/>.
|
||||
/// When a custom view is provided, the host application will use it directly
|
||||
/// instead of generating a page from the declared options.
|
||||
/// </summary>
|
||||
/// <param name="viewType">A <see cref="SettingsPageBase"/> subclass type that defines the settings UI.</param>
|
||||
public PluginSettingsSectionBuilder SetCustomView(Type viewType)
|
||||
/// <param name="viewType">A <see cref="AirAppSettingsPageBase"/> subclass type that defines the settings UI.</param>
|
||||
public AirAppSettingsSectionBuilder SetCustomView(Type viewType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(viewType);
|
||||
|
||||
if (!typeof(SettingsPageBase).IsAssignableFrom(viewType))
|
||||
if (!typeof(AirAppSettingsPageBase).IsAssignableFrom(viewType))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Custom view type must be a subclass of {nameof(SettingsPageBase)}.",
|
||||
$"Custom view type must be a subclass of {nameof(AirAppSettingsPageBase)}.",
|
||||
nameof(viewType));
|
||||
}
|
||||
|
||||
@@ -73,44 +73,44 @@ public sealed class PluginSettingsSectionBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public PluginSettingsSectionBuilder AddOption(SettingsOptionDefinition option)
|
||||
public AirAppSettingsSectionBuilder AddOption(AirAppSettingOptionDefinition option)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(option);
|
||||
_options.Add(option);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PluginSettingsSectionBuilder AddToggle(
|
||||
public AirAppSettingsSectionBuilder AddToggle(
|
||||
string key,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
bool defaultValue = false)
|
||||
{
|
||||
return AddOption(new SettingsOptionDefinition(
|
||||
return AddOption(new AirAppSettingOptionDefinition(
|
||||
key,
|
||||
SettingsOptionType.Toggle,
|
||||
AirAppSettingOptionType.Toggle,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
defaultValue));
|
||||
}
|
||||
|
||||
public PluginSettingsSectionBuilder AddText(
|
||||
public AirAppSettingsSectionBuilder AddText(
|
||||
string key,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
string defaultValue = "",
|
||||
string? validationPattern = null)
|
||||
{
|
||||
return AddOption(new SettingsOptionDefinition(
|
||||
return AddOption(new AirAppSettingOptionDefinition(
|
||||
key,
|
||||
SettingsOptionType.Text,
|
||||
AirAppSettingOptionType.Text,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
defaultValue,
|
||||
validationPattern: validationPattern));
|
||||
}
|
||||
|
||||
public PluginSettingsSectionBuilder AddNumber(
|
||||
public AirAppSettingsSectionBuilder AddNumber(
|
||||
string key,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
@@ -118,9 +118,9 @@ public sealed class PluginSettingsSectionBuilder
|
||||
double? minimum = null,
|
||||
double? maximum = null)
|
||||
{
|
||||
return AddOption(new SettingsOptionDefinition(
|
||||
return AddOption(new AirAppSettingOptionDefinition(
|
||||
key,
|
||||
SettingsOptionType.Number,
|
||||
AirAppSettingOptionType.Number,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
defaultValue,
|
||||
@@ -128,56 +128,56 @@ public sealed class PluginSettingsSectionBuilder
|
||||
maximum: maximum));
|
||||
}
|
||||
|
||||
public PluginSettingsSectionBuilder AddSelect(
|
||||
public AirAppSettingsSectionBuilder AddSelect(
|
||||
string key,
|
||||
string titleLocalizationKey,
|
||||
IEnumerable<SettingsOptionChoice> choices,
|
||||
IEnumerable<AirAppSettingOptionChoice> choices,
|
||||
string? descriptionLocalizationKey = null,
|
||||
string? defaultValue = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(choices);
|
||||
var normalizedChoices = choices.ToArray();
|
||||
|
||||
return AddOption(new SettingsOptionDefinition(
|
||||
return AddOption(new AirAppSettingOptionDefinition(
|
||||
key,
|
||||
SettingsOptionType.Select,
|
||||
AirAppSettingOptionType.Select,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
defaultValue,
|
||||
normalizedChoices));
|
||||
}
|
||||
|
||||
public PluginSettingsSectionBuilder AddPath(
|
||||
public AirAppSettingsSectionBuilder AddPath(
|
||||
string key,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
string defaultValue = "")
|
||||
{
|
||||
return AddOption(new SettingsOptionDefinition(
|
||||
return AddOption(new AirAppSettingOptionDefinition(
|
||||
key,
|
||||
SettingsOptionType.Path,
|
||||
AirAppSettingOptionType.Path,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
defaultValue));
|
||||
}
|
||||
|
||||
public PluginSettingsSectionBuilder AddList(
|
||||
public AirAppSettingsSectionBuilder AddList(
|
||||
string key,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
IReadOnlyList<string>? defaultValue = null)
|
||||
{
|
||||
return AddOption(new SettingsOptionDefinition(
|
||||
return AddOption(new AirAppSettingOptionDefinition(
|
||||
key,
|
||||
SettingsOptionType.List,
|
||||
AirAppSettingOptionType.List,
|
||||
titleLocalizationKey,
|
||||
descriptionLocalizationKey,
|
||||
defaultValue ?? Array.Empty<string>()));
|
||||
}
|
||||
|
||||
internal PluginSettingsSectionRegistration Build()
|
||||
internal AirAppSettingsSectionRegistration Build()
|
||||
{
|
||||
return new PluginSettingsSectionRegistration(
|
||||
return new AirAppSettingsSectionRegistration(
|
||||
Id,
|
||||
TitleLocalizationKey,
|
||||
_options.ToArray(),
|
||||
@@ -1,19 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class SettingsSectionDefinition
|
||||
public sealed class AirAppSettingsSectionDefinition
|
||||
{
|
||||
public SettingsSectionDefinition(
|
||||
public AirAppSettingsSectionDefinition(
|
||||
string id,
|
||||
string category,
|
||||
SettingsScope scope,
|
||||
AirAppSettingsScope scope,
|
||||
string titleLocalizationKey,
|
||||
string? descriptionLocalizationKey = null,
|
||||
string iconKey = "Settings",
|
||||
int sortOrder = 0,
|
||||
string? subjectId = null,
|
||||
IReadOnlyList<SettingsOptionDefinition>? options = null)
|
||||
IReadOnlyList<AirAppSettingOptionDefinition>? options = null)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(id);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(category);
|
||||
@@ -37,7 +37,7 @@ public sealed class SettingsSectionDefinition
|
||||
|
||||
public string Category { get; }
|
||||
|
||||
public SettingsScope Scope { get; }
|
||||
public AirAppSettingsScope Scope { get; }
|
||||
|
||||
public string TitleLocalizationKey { get; }
|
||||
|
||||
@@ -49,5 +49,5 @@ public sealed class SettingsSectionDefinition
|
||||
|
||||
public string? SubjectId { get; }
|
||||
|
||||
public IReadOnlyList<SettingsOptionDefinition> Options { get; }
|
||||
public IReadOnlyList<AirAppSettingOptionDefinition> Options { get; }
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed class PluginSettingsSectionRegistration
|
||||
public sealed class AirAppSettingsSectionRegistration
|
||||
{
|
||||
public PluginSettingsSectionRegistration(
|
||||
public AirAppSettingsSectionRegistration(
|
||||
string id,
|
||||
string titleLocalizationKey,
|
||||
IReadOnlyList<SettingsOptionDefinition> options,
|
||||
IReadOnlyList<AirAppSettingOptionDefinition> options,
|
||||
string? descriptionLocalizationKey = null,
|
||||
string iconKey = "PuzzlePiece",
|
||||
int sortOrder = 0,
|
||||
@@ -27,10 +27,10 @@ public sealed class PluginSettingsSectionRegistration
|
||||
SortOrder = sortOrder;
|
||||
Options = options ?? [];
|
||||
|
||||
if (customViewType is not null && !typeof(SettingsPageBase).IsAssignableFrom(customViewType))
|
||||
if (customViewType is not null && !typeof(AirAppSettingsPageBase).IsAssignableFrom(customViewType))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Custom view type must be a subclass of {nameof(SettingsPageBase)}.",
|
||||
$"Custom view type must be a subclass of {nameof(AirAppSettingsPageBase)}.",
|
||||
nameof(customViewType));
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ public sealed class PluginSettingsSectionRegistration
|
||||
|
||||
public int SortOrder { get; }
|
||||
|
||||
public IReadOnlyList<SettingsOptionDefinition> Options { get; }
|
||||
public IReadOnlyList<AirAppSettingOptionDefinition> Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When set, the host application will instantiate this <see cref="SettingsPageBase"/> subclass
|
||||
/// When set, the host application will instantiate this <see cref="AirAppSettingsPageBase"/> subclass
|
||||
/// instead of generating a page from <see cref="Options"/>.
|
||||
/// This allows plugins to provide fully custom AXAML views with any Fluent Avalonia controls.
|
||||
/// </summary>
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record PluginSharedContractReference(
|
||||
public sealed record AirAppSharedContractReference(
|
||||
string Id,
|
||||
string Version,
|
||||
string AssemblyName)
|
||||
@@ -16,7 +16,7 @@ public sealed record PluginSharedContractReference(
|
||||
[JsonIgnore]
|
||||
public string NormalizedAssemblyName => AssemblyName.Trim();
|
||||
|
||||
internal PluginSharedContractReference NormalizeAndValidate(string manifestPath)
|
||||
internal AirAppSharedContractReference NormalizeAndValidate(string manifestPath)
|
||||
{
|
||||
var normalized = this with
|
||||
{
|
||||
@@ -28,7 +28,7 @@ public sealed record PluginSharedContractReference(
|
||||
if (!System.Version.TryParse(normalized.Version, out _))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Plugin manifest '{manifestPath}' declares invalid shared contract version '{normalized.Version}' for '{normalized.Id}'.");
|
||||
$"AirApp manifest '{manifestPath}' declares invalid shared contract version '{normalized.Version}' for '{normalized.Id}'.");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
@@ -39,7 +39,7 @@ public sealed record PluginSharedContractReference(
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Plugin manifest '{manifestPath}' is missing required shared contract property '{propertyName}'.");
|
||||
$"AirApp manifest '{manifestPath}' is missing required shared contract property '{propertyName}'.");
|
||||
}
|
||||
|
||||
return value.Trim();
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a window entry contributed by an AirApp.
|
||||
/// Produced by <c>AddAirAppWindow<TWindow></c> and resolved by the AirAppHost
|
||||
/// window loader to open the window content inside the host window shell.
|
||||
/// </summary>
|
||||
public sealed record AirAppWindowRegistration(
|
||||
string WindowId,
|
||||
string DisplayName,
|
||||
Type WindowType);
|
||||
@@ -1,14 +1,14 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public abstract class PluginWorkerBase : IPluginWorker
|
||||
public abstract class AirAppWorkerBase : IAirAppWorker
|
||||
{
|
||||
public virtual void ConfigureServices(IPluginWorkerContext context, IServiceCollection services)
|
||||
public virtual void ConfigureServices(IAirAppWorkerContext context, IServiceCollection services)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual Task StartAsync(IPluginWorkerContext context, IServiceProvider services, CancellationToken cancellationToken = default)
|
||||
public virtual Task StartAsync(IAirAppWorkerContext context, IServiceProvider services, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class AirAppWorkerEntranceAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the latest read-only appearance snapshot when host appearance values change.
|
||||
@@ -6,7 +6,7 @@ namespace LanMountainDesktop.PluginSdk;
|
||||
public sealed class AppearanceChangedEvent : EventArgs
|
||||
{
|
||||
public AppearanceChangedEvent(
|
||||
PluginAppearanceSnapshot snapshot,
|
||||
AirAppAppearanceSnapshot snapshot,
|
||||
IReadOnlyCollection<AppearanceProperty> changedProperties)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(snapshot);
|
||||
@@ -16,7 +16,7 @@ public sealed class AppearanceChangedEvent : EventArgs
|
||||
ChangedProperties = changedProperties;
|
||||
}
|
||||
|
||||
public PluginAppearanceSnapshot Snapshot { get; }
|
||||
public AirAppAppearanceSnapshot Snapshot { get; }
|
||||
|
||||
public IReadOnlyCollection<AppearanceProperty> ChangedProperties { get; }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
namespace LanMountainDesktop.PluginIsolation.Contracts;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Wire request for the IPC appearance snapshot payload. This request targets the
|
||||
/// isolation-contract DTOs, not the runtime SDK snapshot with the same type name.
|
||||
/// </summary>
|
||||
public sealed record PluginAppearanceSnapshotRequest(string SessionId);
|
||||
public sealed record AirAppAppearanceSnapshotRequest(string SessionId);
|
||||
|
||||
public sealed record PluginMaterialSurfaceSnapshot(
|
||||
public sealed record AirAppMaterialSurfaceSnapshot(
|
||||
string BackgroundColor,
|
||||
string BorderColor,
|
||||
double BlurRadius,
|
||||
@@ -14,9 +14,9 @@ public sealed record PluginMaterialSurfaceSnapshot(
|
||||
|
||||
/// <summary>
|
||||
/// Wire-format appearance snapshot exchanged over IPC.
|
||||
/// Do not treat this as the same type as <c>LanMountainDesktop.PluginSdk.PluginAppearanceSnapshot</c>.
|
||||
/// Do not treat this as the same type as <c>LanMountainDesktop.AirAppSdk.AirAppAppearanceSnapshot</c>.
|
||||
/// </summary>
|
||||
public sealed record PluginAppearanceSnapshot(
|
||||
public sealed record AirAppAppearanceSnapshot(
|
||||
string ThemeVariant,
|
||||
string? AccentColor = null,
|
||||
double CornerRadiusScale = 1.0,
|
||||
@@ -26,10 +26,10 @@ public sealed record PluginAppearanceSnapshot(
|
||||
string? ColorSource = null,
|
||||
string? SystemMaterialMode = null,
|
||||
IReadOnlyDictionary<string, string>? ColorRoles = null,
|
||||
IReadOnlyDictionary<string, PluginMaterialSurfaceSnapshot>? MaterialSurfaces = null,
|
||||
IReadOnlyDictionary<string, AirAppMaterialSurfaceSnapshot>? MaterialSurfaces = null,
|
||||
IReadOnlyList<string>? WallpaperSeedCandidates = null);
|
||||
|
||||
/// <summary>
|
||||
/// Wire notification carrying the IPC appearance snapshot.
|
||||
/// </summary>
|
||||
public sealed record PluginAppearanceChangedNotification(PluginAppearanceSnapshot Snapshot);
|
||||
public sealed record AirAppAppearanceChangedNotification(AirAppAppearanceSnapshot Snapshot);
|
||||
@@ -1,8 +1,8 @@
|
||||
using Avalonia;
|
||||
using LanMountainDesktop.Settings.Core;
|
||||
using LanMountainDesktop.AirAppSdk;
|
||||
using LanMountainDesktop.Shared.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.Appearance;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class AppearanceCornerRadiusTokenFactory
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
using Avalonia.Metadata;
|
||||
|
||||
[assembly: XmlnsPrefix("http://lanmountain.tech/schemas/xaml/sdk", "lmd")]
|
||||
[assembly: XmlnsDefinition("http://lanmountain.tech/schemas/xaml/sdk", "LanMountainDesktop.PluginSdk")]
|
||||
[assembly: XmlnsDefinition("http://lanmountain.tech/schemas/xaml/sdk", "LanMountainDesktop.AirAppSdk")]
|
||||
@@ -1,21 +1,21 @@
|
||||
namespace LanMountainDesktop.PluginIsolation.Contracts;
|
||||
namespace LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
public sealed record PluginHeartbeatPing(
|
||||
public sealed record AirAppHeartbeatPing(
|
||||
string SessionId,
|
||||
DateTimeOffset SentAtUtc);
|
||||
|
||||
public sealed record PluginHeartbeatPong(
|
||||
public sealed record AirAppHeartbeatPong(
|
||||
string SessionId,
|
||||
DateTimeOffset ReceivedAtUtc);
|
||||
|
||||
public sealed record PluginLogEntry(
|
||||
public sealed record AirAppLogEntry(
|
||||
string Level,
|
||||
string Category,
|
||||
string Message,
|
||||
DateTimeOffset TimestampUtc,
|
||||
string? Exception = null);
|
||||
|
||||
public static class PluginLogLevels
|
||||
public static class AirAppLogLevels
|
||||
{
|
||||
public const string Trace = "trace";
|
||||
public const string Debug = "debug";
|
||||
@@ -25,7 +25,7 @@ public static class PluginLogLevels
|
||||
public const string Critical = "critical";
|
||||
}
|
||||
|
||||
public sealed record PluginFaultReport(
|
||||
public sealed record AirAppFaultReport(
|
||||
string SessionId,
|
||||
string FaultKind,
|
||||
bool IsFatal,
|
||||
@@ -35,7 +35,7 @@ public sealed record PluginFaultReport(
|
||||
int? ExitCode = null,
|
||||
DateTimeOffset? OccurredAtUtc = null);
|
||||
|
||||
public static class PluginFaultKinds
|
||||
public static class AirAppFaultKinds
|
||||
{
|
||||
public const string ManagedException = "managed-exception";
|
||||
public const string NativeCrash = "native-crash";
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Settings.Core;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public static class GlobalAppearanceSettings
|
||||
{
|
||||
@@ -4,7 +4,10 @@ using Microsoft.Extensions.Hosting;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Core interface for AirApp entry point.
|
||||
/// Core interface for the AirApp entry point.
|
||||
/// Implement this interface (or derive from <see cref="AirAppBase"/>) and mark the class
|
||||
/// with <see cref="AirAppEntranceAttribute"/> to expose a desktop component or window
|
||||
/// AirApp to the LanMountainDesktop host.
|
||||
/// </summary>
|
||||
public interface IAirApp
|
||||
{
|
||||
@@ -1,14 +1,14 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// 插件外观上下文接口,提供主题、圆角等外观资源的访问和变更通知。
|
||||
/// </summary>
|
||||
public interface IPluginAppearanceContext
|
||||
public interface IAirAppAppearanceContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前外观快照。
|
||||
/// </summary>
|
||||
PluginAppearanceSnapshot Snapshot { get; }
|
||||
AirAppAppearanceSnapshot Snapshot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 外观变更事件。当主题、圆角或其他外观属性发生变化时触发。
|
||||
@@ -31,5 +31,5 @@ public interface IPluginAppearanceContext
|
||||
/// <param name="minimum">最小值(可选)</param>
|
||||
/// <param name="maximum">最大值(可选)</param>
|
||||
/// <returns>解析后的圆角半径</returns>
|
||||
double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null);
|
||||
double ResolveCornerRadius(AirAppCornerRadiusPreset preset, double? minimum = null, double? maximum = null);
|
||||
}
|
||||
13
airapp/LanMountainDesktop.AirAppSdk/IAirAppExportRegistry.cs
Normal file
13
airapp/LanMountainDesktop.AirAppSdk/IAirAppExportRegistry.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IAirAppExportRegistry
|
||||
{
|
||||
IReadOnlyList<AirAppServiceExportDescriptor> GetExports();
|
||||
|
||||
IReadOnlyList<AirAppServiceExportDescriptor> GetExports(Type contractType);
|
||||
|
||||
AirAppServiceExportDescriptor? GetExport(Type contractType, string providerAirAppId);
|
||||
|
||||
TContract? GetExport<TContract>(string providerAirAppId)
|
||||
where TContract : class;
|
||||
}
|
||||
@@ -2,9 +2,20 @@ namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Message bus for inter-AirApp communication.
|
||||
/// Supports both strong-typed messages and topic-based routing.
|
||||
/// </summary>
|
||||
public interface IAirAppMessageBus
|
||||
{
|
||||
/// <summary>
|
||||
/// Subscribe to a strong-typed message.
|
||||
/// </summary>
|
||||
IDisposable Subscribe<TMessage>(Action<TMessage> handler);
|
||||
|
||||
/// <summary>
|
||||
/// Publish a strong-typed message.
|
||||
/// </summary>
|
||||
void Publish<TMessage>(TMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// Publish a message to a topic.
|
||||
/// </summary>
|
||||
@@ -21,7 +32,7 @@ public interface IAirAppMessageBus
|
||||
IDisposable Subscribe(string topic, Action<object?> handler);
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to a topic with typed payload.
|
||||
/// Subscribe to a topic with a typed payload.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Payload type</typeparam>
|
||||
/// <param name="topic">Message topic</param>
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IAirAppPackageManager
|
||||
{
|
||||
IReadOnlyList<AirAppInstalledInfo> GetInstalledAirApps();
|
||||
|
||||
AirAppPackageInstallResult InstallPackage(string packagePath);
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IPluginPublicIpcBuilder
|
||||
public interface IAirAppPublicIpcBuilder
|
||||
{
|
||||
IPluginPublicIpcBuilder AddService<TContract>(
|
||||
IAirAppPublicIpcBuilder AddService<TContract>(
|
||||
string? objectId = null,
|
||||
IEnumerable<string>? notifyIds = null)
|
||||
where TContract : class;
|
||||
|
||||
IPluginPublicIpcBuilder AddService(
|
||||
IAirAppPublicIpcBuilder AddService(
|
||||
Type contractType,
|
||||
object implementation,
|
||||
string? objectId = null,
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IAirAppPublicIpcContributor
|
||||
{
|
||||
void ConfigurePublicIpc(IAirAppPublicIpcBuilder builder);
|
||||
}
|
||||
@@ -4,23 +4,20 @@ namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
/// <summary>
|
||||
/// Provides runtime context and services for an AirApp.
|
||||
/// The host hands an implementation to <see cref="IAirApp.OnStartedAsync"/> and exposes it
|
||||
/// through the per-AirApp service container.
|
||||
/// </summary>
|
||||
public interface IAirAppRuntimeContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of this AirApp.
|
||||
/// Gets the manifest of this AirApp.
|
||||
/// </summary>
|
||||
string AirAppId { get; }
|
||||
AirAppManifest Manifest { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name of this AirApp.
|
||||
/// Gets the directory that contains the loaded AirApp package.
|
||||
/// </summary>
|
||||
string AirAppName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AirApp version.
|
||||
/// </summary>
|
||||
string AirAppVersion { get; }
|
||||
string AirAppDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data directory for this AirApp.
|
||||
@@ -39,6 +36,11 @@ public interface IAirAppRuntimeContext
|
||||
/// </summary>
|
||||
IServiceProvider Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a snapshot of the host-provided properties for this AirApp.
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, object?> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the host application lifetime manager.
|
||||
/// </summary>
|
||||
@@ -59,6 +61,16 @@ public interface IAirAppRuntimeContext
|
||||
/// </summary>
|
||||
IAirAppLogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a service from the AirApp service provider.
|
||||
/// </summary>
|
||||
T? GetService<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Try to read a host-provided property.
|
||||
/// </summary>
|
||||
bool TryGetProperty<T>(string key, out T? value);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a window defined by this AirApp.
|
||||
/// </summary>
|
||||
@@ -73,17 +85,17 @@ public interface IAirAppRuntimeContext
|
||||
void CloseWindow(string windowId);
|
||||
|
||||
/// <summary>
|
||||
/// Register a desktop component (internal use by AirAppBase).
|
||||
/// Register a desktop component (internal use by <see cref="AirAppBase"/>).
|
||||
/// </summary>
|
||||
void RegisterComponent(AirAppComponentOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Register a window (internal use by AirAppBase).
|
||||
/// Register a window (internal use by <see cref="AirAppBase"/>).
|
||||
/// </summary>
|
||||
void RegisterWindow(string id, string name, Type windowType);
|
||||
|
||||
/// <summary>
|
||||
/// Register a service (internal use by AirAppBase).
|
||||
/// Register a service (internal use by <see cref="AirAppBase"/>).
|
||||
/// </summary>
|
||||
void RegisterService<TService, TImplementation>()
|
||||
where TService : class
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IPluginSettingsService
|
||||
public interface IAirAppSettingsService
|
||||
{
|
||||
string PluginId { get; }
|
||||
string AirAppId { get; }
|
||||
|
||||
IComponentSettingsAccessor GetComponentAccessor(string componentId, string? placementId);
|
||||
|
||||
@@ -12,6 +12,13 @@ public interface IAirAppWindow
|
||||
/// </summary>
|
||||
AirAppWindowDescriptor Descriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root content hosted inside the AirAppHost window shell.
|
||||
/// <see cref="AirAppWindowBase"/> inherits from Avalonia <c>Window</c> and satisfies this
|
||||
/// through <c>Window.Content</c>.
|
||||
/// </summary>
|
||||
object? Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called before the window is opened.
|
||||
/// Use this for async initialization.
|
||||
@@ -1,12 +1,12 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IPluginWorker
|
||||
public interface IAirAppWorker
|
||||
{
|
||||
void ConfigureServices(IPluginWorkerContext context, IServiceCollection services);
|
||||
void ConfigureServices(IAirAppWorkerContext context, IServiceCollection services);
|
||||
|
||||
Task StartAsync(IPluginWorkerContext context, IServiceProvider services, CancellationToken cancellationToken = default);
|
||||
Task StartAsync(IAirAppWorkerContext context, IServiceProvider services, CancellationToken cancellationToken = default);
|
||||
|
||||
Task StopAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
26
airapp/LanMountainDesktop.AirAppSdk/IAirAppWorkerContext.cs
Normal file
26
airapp/LanMountainDesktop.AirAppSdk/IAirAppWorkerContext.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using LanMountainDesktop.AirAppIsolation.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IAirAppWorkerContext
|
||||
{
|
||||
string AirAppId { get; }
|
||||
|
||||
AirAppManifest Manifest { get; }
|
||||
|
||||
AirAppRuntimeMode RuntimeMode { get; }
|
||||
|
||||
string SessionId { get; }
|
||||
|
||||
string HostPipeName { get; }
|
||||
|
||||
string ProtocolVersion { get; }
|
||||
|
||||
string AirAppDirectory { get; }
|
||||
|
||||
string DataDirectory { get; }
|
||||
|
||||
IReadOnlyList<AirAppCapabilityDeclaration> GrantedCapabilities { get; }
|
||||
|
||||
IReadOnlyDictionary<string, string> StartupProperties { get; }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IComponentEditorHostContext
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IComponentSettingsAccessor
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.Host.Abstractions;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface IDesktopShellHost
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public sealed record HostApplicationLifecycleRequest(
|
||||
string? Source = null,
|
||||
10
airapp/LanMountainDesktop.AirAppSdk/ISettingsCatalog.cs
Normal file
10
airapp/LanMountainDesktop.AirAppSdk/ISettingsCatalog.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LanMountainDesktop.AirAppSdk;
|
||||
|
||||
public interface ISettingsCatalog
|
||||
{
|
||||
IReadOnlyList<AirAppSettingsSectionDefinition> GetSections();
|
||||
|
||||
IReadOnlyList<AirAppSettingsSectionDefinition> GetSections(AirAppSettingsScope scope);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user