mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
feat.PLONDS在线安装器继续优化
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:theme="using:Avalonia.Themes.Fluent"
|
xmlns:theme="using:Avalonia.Themes.Fluent"
|
||||||
xmlns:fi="using:FluentIcons.Avalonia"
|
|
||||||
x:Class="LanDesktopPLONDS.Installer.App"
|
x:Class="LanDesktopPLONDS.Installer.App"
|
||||||
RequestedThemeVariant="Default">
|
RequestedThemeVariant="Default">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<FontFamily x:Key="AppFontFamily">Inter, Segoe UI, Microsoft YaHei UI</FontFamily>
|
<FontFamily x:Key="AppFontFamily">Segoe UI, Microsoft YaHei UI</FontFamily>
|
||||||
|
<FontFamily x:Key="InstallerIconFontFamily">Segoe MDL2 Assets</FontFamily>
|
||||||
<CornerRadius x:Key="DesignCornerRadiusMicro">2</CornerRadius>
|
<CornerRadius x:Key="DesignCornerRadiusMicro">2</CornerRadius>
|
||||||
<CornerRadius x:Key="DesignCornerRadiusXs">4</CornerRadius>
|
<CornerRadius x:Key="DesignCornerRadiusXs">4</CornerRadius>
|
||||||
<CornerRadius x:Key="DesignCornerRadiusSm">4</CornerRadius>
|
<CornerRadius x:Key="DesignCornerRadiusSm">4</CornerRadius>
|
||||||
@@ -76,10 +76,12 @@
|
|||||||
<Style Selector="UserControl">
|
<Style Selector="UserControl">
|
||||||
<Setter Property="FontFamily" Value="{DynamicResource AppFontFamily}" />
|
<Setter Property="FontFamily" Value="{DynamicResource AppFontFamily}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="fi|FluentIcon">
|
<Style Selector="TextBlock.installer-icon">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
||||||
|
<Setter Property="FontFamily" Value="{DynamicResource InstallerIconFontFamily}" />
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||||
|
<Setter Property="TextAlignment" Value="Center" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBlock">
|
<Style Selector="TextBlock">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
||||||
@@ -142,13 +144,13 @@
|
|||||||
<Setter Property="Background" Value="{DynamicResource InstallerSubtleFillBrush}" />
|
<Setter Property="Background" Value="{DynamicResource InstallerSubtleFillBrush}" />
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.primary-command fi|FluentIcon">
|
<Style Selector="Button.primary-command TextBlock.installer-icon">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerOnAccentBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerOnAccentBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.primary-command TextBlock">
|
<Style Selector="Button.primary-command TextBlock">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerOnAccentBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerOnAccentBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.primary-command:disabled fi|FluentIcon">
|
<Style Selector="Button.primary-command:disabled TextBlock.installer-icon">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.primary-command:disabled TextBlock">
|
<Style Selector="Button.primary-command:disabled TextBlock">
|
||||||
@@ -174,13 +176,13 @@
|
|||||||
<Style Selector="Button.secondary-command TextBlock">
|
<Style Selector="Button.secondary-command TextBlock">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.secondary-command fi|FluentIcon">
|
<Style Selector="Button.secondary-command TextBlock.installer-icon">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerTextPrimaryBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.secondary-command:disabled TextBlock">
|
<Style Selector="Button.secondary-command:disabled TextBlock">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.secondary-command:disabled fi|FluentIcon">
|
<Style Selector="Button.secondary-command:disabled TextBlock.installer-icon">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBox">
|
<Style Selector="TextBox">
|
||||||
|
|||||||
92
LanDesktopPLONDS.installer/InstallerStartupDiagnostics.cs
Normal file
92
LanDesktopPLONDS.installer/InstallerStartupDiagnostics.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LanDesktopPLONDS.Installer;
|
||||||
|
|
||||||
|
internal static class InstallerStartupDiagnostics
|
||||||
|
{
|
||||||
|
private const uint MessageBoxIconError = 0x00000010;
|
||||||
|
private const uint MessageBoxOk = 0x00000000;
|
||||||
|
|
||||||
|
private static int _initialized;
|
||||||
|
private static int _fatalMessageShown;
|
||||||
|
|
||||||
|
public static string LogPath => Path.Combine(GetLogDirectory(), "startup.log");
|
||||||
|
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _initialized, 1) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
|
||||||
|
{
|
||||||
|
var exception = args.ExceptionObject as Exception;
|
||||||
|
ReportFatal("The installer encountered an unhandled startup error.", exception);
|
||||||
|
};
|
||||||
|
|
||||||
|
TaskScheduler.UnobservedTaskException += (_, args) =>
|
||||||
|
{
|
||||||
|
ReportFatal("The installer encountered an unobserved background error.", args.Exception);
|
||||||
|
args.SetObserved();
|
||||||
|
};
|
||||||
|
|
||||||
|
Log("Startup diagnostics initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Log(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(GetLogDirectory());
|
||||||
|
File.AppendAllText(
|
||||||
|
LogPath,
|
||||||
|
$"[{DateTimeOffset.Now:O}] {message}{Environment.NewLine}",
|
||||||
|
Encoding.UTF8);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Diagnostics must never become the reason the installer cannot start.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReportFatal(string message, Exception? exception)
|
||||||
|
{
|
||||||
|
Log(exception is null ? message : $"{message}{Environment.NewLine}{exception}");
|
||||||
|
|
||||||
|
if (!OperatingSystem.IsWindows() || Interlocked.Exchange(ref _fatalMessageShown, 1) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var details = exception is null
|
||||||
|
? message
|
||||||
|
: $"{message}{Environment.NewLine}{Environment.NewLine}{exception.GetType().Name}: {exception.Message}";
|
||||||
|
_ = MessageBox(
|
||||||
|
IntPtr.Zero,
|
||||||
|
$"{details}{Environment.NewLine}{Environment.NewLine}Log: {LogPath}",
|
||||||
|
"LanDesktopPLONDS Installer",
|
||||||
|
MessageBoxOk | MessageBoxIconError);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLogDirectory()
|
||||||
|
{
|
||||||
|
var root = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
|
if (string.IsNullOrWhiteSpace(root))
|
||||||
|
{
|
||||||
|
root = AppContext.BaseDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(root, "LanMountainDesktop", "Installer", "logs");
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll", EntryPoint = "MessageBoxW", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
|
||||||
|
}
|
||||||
@@ -8,7 +8,14 @@
|
|||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
||||||
<OptimizationPreference>Size</OptimizationPreference>
|
<OptimizationPreference>Size</OptimizationPreference>
|
||||||
|
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
|
||||||
<PublishReadyToRun>false</PublishReadyToRun>
|
<PublishReadyToRun>false</PublishReadyToRun>
|
||||||
|
<DebuggerSupport>false</DebuggerSupport>
|
||||||
|
<EventSourceSupport>false</EventSourceSupport>
|
||||||
|
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
|
||||||
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
|
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||||
|
<UseSystemResourceKeys>true</UseSystemResourceKeys>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -16,24 +23,11 @@
|
|||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(PublishAot)' == 'true'">
|
|
||||||
<TrimmerRootAssembly Include="Avalonia" />
|
|
||||||
<TrimmerRootAssembly Include="Avalonia.Desktop" />
|
|
||||||
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
|
|
||||||
<TrimmerRootAssembly Include="FluentIcons.Avalonia" />
|
|
||||||
<TrimmerRootAssembly Include="LanDesktopPLONDS.installer" />
|
|
||||||
<TrimmerRootAssembly Include="System.Text.Json" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Target
|
<Target
|
||||||
Name="PrepareInstallerEmbeddedNativeLibraries"
|
Name="PrepareInstallerEmbeddedNativeLibraries"
|
||||||
BeforeTargets="AssignTargetPaths"
|
BeforeTargets="AssignTargetPaths"
|
||||||
Condition="'$(PublishAot)' == 'true' and '$(RuntimeIdentifier)' == 'win-x64'">
|
Condition="'$(PublishAot)' == 'true' and '$(RuntimeIdentifier)' == 'win-x64'">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<InstallerNativeLibrary
|
|
||||||
Include="$(PkgAvalonia_Angle_Windows_Natives)\runtimes\win-x64\native\av_libglesv2.dll"
|
|
||||||
CompressedName="av_libglesv2.dll.gz"
|
|
||||||
Condition="Exists('$(PkgAvalonia_Angle_Windows_Natives)\runtimes\win-x64\native\av_libglesv2.dll')" />
|
|
||||||
<InstallerNativeLibrary
|
<InstallerNativeLibrary
|
||||||
Include="$(PkgHarfBuzzSharp_NativeAssets_Win32)\runtimes\win-x64\native\libHarfBuzzSharp.dll"
|
Include="$(PkgHarfBuzzSharp_NativeAssets_Win32)\runtimes\win-x64\native\libHarfBuzzSharp.dll"
|
||||||
CompressedName="libHarfBuzzSharp.dll.gz"
|
CompressedName="libHarfBuzzSharp.dll.gz"
|
||||||
@@ -53,9 +47,6 @@
|
|||||||
Command="powershell -NoProfile -ExecutionPolicy Bypass -File "$(MSBuildThisFileDirectory)Compress-NativeLibrary.ps1" -SourcePath "%(InstallerNativeLibrary.FullPath)" -DestinationPath "$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\%(InstallerNativeLibrary.CompressedName)"" />
|
Command="powershell -NoProfile -ExecutionPolicy Bypass -File "$(MSBuildThisFileDirectory)Compress-NativeLibrary.ps1" -SourcePath "%(InstallerNativeLibrary.FullPath)" -DestinationPath "$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\%(InstallerNativeLibrary.CompressedName)"" />
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource
|
|
||||||
Include="$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\av_libglesv2.dll.gz"
|
|
||||||
LogicalName="LanDesktopPLONDS.Installer.NativeLibraries.av_libglesv2.dll.gz" />
|
|
||||||
<EmbeddedResource
|
<EmbeddedResource
|
||||||
Include="$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\libHarfBuzzSharp.dll.gz"
|
Include="$(IntermediateOutputPath)embedded-native\$(RuntimeIdentifier)\libHarfBuzzSharp.dll.gz"
|
||||||
LogicalName="LanDesktopPLONDS.Installer.NativeLibraries.libHarfBuzzSharp.dll.gz" />
|
LogicalName="LanDesktopPLONDS.Installer.NativeLibraries.libHarfBuzzSharp.dll.gz" />
|
||||||
@@ -68,7 +59,7 @@
|
|||||||
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
|
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
|
||||||
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
|
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
|
||||||
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
||||||
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
|
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
|
||||||
<IsAotCompatible>true</IsAotCompatible>
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -20,11 +20,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" />
|
<PackageReference Include="Avalonia" />
|
||||||
<PackageReference Include="Avalonia.Angle.Windows.Natives" GeneratePathProperty="true" PrivateAssets="all" />
|
<PackageReference Include="Avalonia.Angle.Windows.Natives" ExcludeAssets="all" PrivateAssets="all" />
|
||||||
<PackageReference Include="Avalonia.Desktop" />
|
<PackageReference Include="Avalonia.Desktop" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" />
|
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" />
|
<PackageReference Include="Avalonia.Themes.Fluent" />
|
||||||
<PackageReference Include="FluentIcons.Avalonia" />
|
|
||||||
<PackageReference Include="HarfBuzzSharp.NativeAssets.Win32" GeneratePathProperty="true" PrivateAssets="all" />
|
<PackageReference Include="HarfBuzzSharp.NativeAssets.Win32" GeneratePathProperty="true" PrivateAssets="all" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Win32" GeneratePathProperty="true" PrivateAssets="all" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Win32" GeneratePathProperty="true" PrivateAssets="all" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ internal static class NativeDependencyBootstrapper
|
|||||||
|
|
||||||
private static readonly string[] NativeLibraryNames =
|
private static readonly string[] NativeLibraryNames =
|
||||||
[
|
[
|
||||||
"av_libglesv2.dll",
|
|
||||||
"libHarfBuzzSharp.dll",
|
"libHarfBuzzSharp.dll",
|
||||||
"libSkiaSharp.dll"
|
"libSkiaSharp.dll"
|
||||||
];
|
];
|
||||||
@@ -47,7 +46,7 @@ internal static class NativeDependencyBootstrapper
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"[NativeDependencyBootstrapper] Failed to prepare native dependencies: {ex}");
|
InstallerStartupDiagnostics.Log($"Native dependency preparation failed: {ex}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Win32;
|
||||||
|
|
||||||
namespace LanDesktopPLONDS.Installer;
|
namespace LanDesktopPLONDS.Installer;
|
||||||
|
|
||||||
@@ -7,17 +8,21 @@ public static class Program
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
InstallerStartupDiagnostics.Initialize();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
InstallerStartupDiagnostics.Log("Preparing native dependencies.");
|
||||||
if (!NativeDependencyBootstrapper.TryPrepare())
|
if (!NativeDependencyBootstrapper.TryPrepare())
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("[Program] Failed to prepare native dependencies, but continuing...");
|
throw new InvalidOperationException("Failed to prepare native dependencies.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstallerStartupDiagnostics.Log("Starting Avalonia desktop lifetime.");
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"[Program] Unhandled exception: {ex}");
|
InstallerStartupDiagnostics.ReportFatal("The installer failed to start.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +30,10 @@ public static class Program
|
|||||||
{
|
{
|
||||||
return AppBuilder.Configure<App>()
|
return AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.WithInterFont()
|
.With(new Win32PlatformOptions
|
||||||
.LogToTrace();
|
{
|
||||||
|
RenderingMode = [Win32RenderingMode.Software],
|
||||||
|
CompositionMode = [Win32CompositionMode.RedirectionSurface]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ internal sealed class FilesPackageInstaller
|
|||||||
var sourceAppDirectory = ResolveFullPackageAppDirectory(package.ExtractDirectory, package.Version);
|
var sourceAppDirectory = ResolveFullPackageAppDirectory(package.ExtractDirectory, package.Version);
|
||||||
var targetDeployment = BuildDeploymentDirectory(launcherRoot, package.Version);
|
var targetDeployment = BuildDeploymentDirectory(launcherRoot, package.Version);
|
||||||
|
|
||||||
|
InstallerElevation.EnsureCanInstall(launcherRoot);
|
||||||
InstallerPathGuard.EnsureUsableInstallPath(launcherRoot, EstimateRequiredBytes(sourceAppDirectory));
|
InstallerPathGuard.EnsureUsableInstallPath(launcherRoot, EstimateRequiredBytes(sourceAppDirectory));
|
||||||
Directory.CreateDirectory(launcherRoot);
|
Directory.CreateDirectory(launcherRoot);
|
||||||
await CopyLauncherRootPayloadAsync(package.ExtractDirectory, sourceAppDirectory, launcherRoot, package.Version, progress, cancellationToken)
|
await CopyLauncherRootPayloadAsync(package.ExtractDirectory, sourceAppDirectory, launcherRoot, package.Version, progress, cancellationToken)
|
||||||
@@ -299,7 +300,9 @@ internal sealed class FilesPackageInstaller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var startMenu = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu);
|
var startMenu = InstallerElevation.IsRunningElevated()
|
||||||
|
? Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu)
|
||||||
|
: Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
|
||||||
if (string.IsNullOrWhiteSpace(startMenu))
|
if (string.IsNullOrWhiteSpace(startMenu))
|
||||||
{
|
{
|
||||||
startMenu = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
|
startMenu = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
|
||||||
|
|||||||
52
LanDesktopPLONDS.installer/Services/InstallerElevation.cs
Normal file
52
LanDesktopPLONDS.installer/Services/InstallerElevation.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Security.Principal;
|
||||||
|
|
||||||
|
namespace LanDesktopPLONDS.Installer.Services;
|
||||||
|
|
||||||
|
internal static class InstallerElevation
|
||||||
|
{
|
||||||
|
public static bool IsRunningElevated()
|
||||||
|
{
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var identity = WindowsIdentity.GetCurrent();
|
||||||
|
var principal = new WindowsPrincipal(identity);
|
||||||
|
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool RequiresElevation(string installPath)
|
||||||
|
{
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullPath = Path.GetFullPath(installPath);
|
||||||
|
return IsUnderSpecialFolder(fullPath, Environment.SpecialFolder.ProgramFiles)
|
||||||
|
|| IsUnderSpecialFolder(fullPath, Environment.SpecialFolder.ProgramFilesX86)
|
||||||
|
|| IsUnderWindowsDirectory(fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnsureCanInstall(string installPath)
|
||||||
|
{
|
||||||
|
if (RequiresElevation(installPath) && !IsRunningElevated())
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException(
|
||||||
|
"The selected installation path requires administrator permission. Restart the installer as administrator or choose a user-writable folder.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsUnderSpecialFolder(string fullPath, Environment.SpecialFolder folder)
|
||||||
|
{
|
||||||
|
var root = Environment.GetFolderPath(folder);
|
||||||
|
return !string.IsNullOrWhiteSpace(root) && InstallerPathGuard.IsSameOrChildPath(root, fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsUnderWindowsDirectory(string fullPath)
|
||||||
|
{
|
||||||
|
var windows = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
||||||
|
return !string.IsNullOrWhiteSpace(windows) && InstallerPathGuard.IsSameOrChildPath(windows, fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,15 +6,13 @@ public static class InstallerPathGuard
|
|||||||
|
|
||||||
public static string GetDefaultInstallPath()
|
public static string GetDefaultInstallPath()
|
||||||
{
|
{
|
||||||
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
if (string.IsNullOrWhiteSpace(programFiles))
|
if (string.IsNullOrWhiteSpace(localAppData))
|
||||||
{
|
{
|
||||||
programFiles = Path.Combine(
|
localAppData = AppContext.BaseDirectory;
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
||||||
"Programs");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Path.Combine(programFiles, ApplicationDirectoryName);
|
return Path.Combine(localAppData, "Programs", ApplicationDirectoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetInstallPathForSelectedFolder(string selectedFolder)
|
public static string GetInstallPathForSelectedFolder(string selectedFolder)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using FluentIcons.Common;
|
|
||||||
using LanDesktopPLONDS.Installer.Models;
|
using LanDesktopPLONDS.Installer.Models;
|
||||||
|
|
||||||
namespace LanDesktopPLONDS.Installer.ViewModels;
|
namespace LanDesktopPLONDS.Installer.ViewModels;
|
||||||
@@ -7,7 +6,7 @@ namespace LanDesktopPLONDS.Installer.ViewModels;
|
|||||||
public sealed partial class InstallerStepViewModel(
|
public sealed partial class InstallerStepViewModel(
|
||||||
InstallerStepId stepId,
|
InstallerStepId stepId,
|
||||||
string title,
|
string title,
|
||||||
Icon icon) : ObservableObject
|
string iconGlyph) : ObservableObject
|
||||||
{
|
{
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isUnlocked;
|
private bool _isUnlocked;
|
||||||
@@ -19,5 +18,5 @@ public sealed partial class InstallerStepViewModel(
|
|||||||
|
|
||||||
public string Title { get; } = title;
|
public string Title { get; } = title;
|
||||||
|
|
||||||
public Icon Icon { get; } = icon;
|
public string IconGlyph { get; } = iconGlyph;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using FluentIcons.Common;
|
|
||||||
using LanDesktopPLONDS.Installer.Models;
|
using LanDesktopPLONDS.Installer.Models;
|
||||||
using LanDesktopPLONDS.Installer.Services;
|
using LanDesktopPLONDS.Installer.Services;
|
||||||
using LanMountainDesktop.Shared.Contracts.Privacy;
|
using LanMountainDesktop.Shared.Contracts.Privacy;
|
||||||
@@ -81,11 +80,11 @@ public sealed partial class MainWindowViewModel : ObservableObject
|
|||||||
_privacyConsentStore = privacyConsentStore ?? new InstallerPrivacyConsentStore();
|
_privacyConsentStore = privacyConsentStore ?? new InstallerPrivacyConsentStore();
|
||||||
Steps =
|
Steps =
|
||||||
[
|
[
|
||||||
new InstallerStepViewModel(InstallerStepId.Welcome, "开始安装", Icon.Play),
|
new InstallerStepViewModel(InstallerStepId.Welcome, "开始安装", "\uE768"),
|
||||||
new InstallerStepViewModel(InstallerStepId.InstallLocation, "安装位置", Icon.Folder),
|
new InstallerStepViewModel(InstallerStepId.InstallLocation, "安装位置", "\uE838"),
|
||||||
new InstallerStepViewModel(InstallerStepId.PrivacyConfirm, "数据确认", Icon.Info),
|
new InstallerStepViewModel(InstallerStepId.PrivacyConfirm, "数据确认", "\uE946"),
|
||||||
new InstallerStepViewModel(InstallerStepId.Deploy, "开始部署", Icon.ArrowDownload),
|
new InstallerStepViewModel(InstallerStepId.Deploy, "开始部署", "\uE896"),
|
||||||
new InstallerStepViewModel(InstallerStepId.Complete, "完成安装", Icon.Circle)
|
new InstallerStepViewModel(InstallerStepId.Complete, "完成安装", "\uE73E")
|
||||||
];
|
];
|
||||||
SyncSteps();
|
SyncSteps();
|
||||||
DeviceIdPreview = _privacyIdentity.GetOrCreateDeviceId();
|
DeviceIdPreview = _privacyIdentity.GetOrCreateDeviceId();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:fi="using:FluentIcons.Avalonia"
|
|
||||||
xmlns:vm="using:LanDesktopPLONDS.Installer.ViewModels"
|
xmlns:vm="using:LanDesktopPLONDS.Installer.ViewModels"
|
||||||
x:Class="LanDesktopPLONDS.Installer.Views.MainWindow"
|
x:Class="LanDesktopPLONDS.Installer.Views.MainWindow"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
<Style Selector="Button.step-nav-item:disabled TextBlock.step-title">
|
<Style Selector="Button.step-nav-item:disabled TextBlock.step-title">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.step-nav-item:disabled fi|FluentIcon">
|
<Style Selector="Button.step-nav-item:disabled TextBlock.installer-icon">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource InstallerDisabledTextBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Border.step-nav-selected-fill">
|
<Style Selector="Border.step-nav-selected-fill">
|
||||||
@@ -129,10 +128,10 @@
|
|||||||
Height="28"
|
Height="28"
|
||||||
Background="{DynamicResource InstallerAccentBrush}"
|
Background="{DynamicResource InstallerAccentBrush}"
|
||||||
CornerRadius="{DynamicResource DesignCornerRadiusSm}">
|
CornerRadius="{DynamicResource DesignCornerRadiusSm}">
|
||||||
<fi:FluentIcon Icon="ArrowDownload"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text=""
|
||||||
Foreground="{DynamicResource InstallerOnAccentBrush}"
|
Foreground="{DynamicResource InstallerOnAccentBrush}"
|
||||||
FontSize="16" />
|
FontSize="16" />
|
||||||
</Border>
|
</Border>
|
||||||
<TextBlock Text="{Binding WindowTitle}"
|
<TextBlock Text="{Binding WindowTitle}"
|
||||||
FontSize="13"
|
FontSize="13"
|
||||||
@@ -148,16 +147,16 @@
|
|||||||
<Button Classes="titlebar-icon-button"
|
<Button Classes="titlebar-icon-button"
|
||||||
ToolTip.Tip="最小化"
|
ToolTip.Tip="最小化"
|
||||||
Click="OnMinimizeClick">
|
Click="OnMinimizeClick">
|
||||||
<fi:FluentIcon Icon="Subtract"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text=""
|
||||||
FontSize="14" />
|
FontSize="14" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button Classes="titlebar-icon-button"
|
<Button Classes="titlebar-icon-button"
|
||||||
ToolTip.Tip="关闭"
|
ToolTip.Tip="关闭"
|
||||||
Click="OnCloseClick">
|
Click="OnCloseClick">
|
||||||
<fi:FluentIcon Icon="Dismiss"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text=""
|
||||||
FontSize="14" />
|
FontSize="14" />
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -196,16 +195,16 @@
|
|||||||
Margin="10,0">
|
Margin="10,0">
|
||||||
<Grid Width="18"
|
<Grid Width="18"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
<fi:FluentIcon Icon="{Binding Icon}"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text="{Binding IconGlyph}"
|
||||||
Foreground="{DynamicResource InstallerTextSecondaryBrush}"
|
Foreground="{DynamicResource InstallerTextSecondaryBrush}"
|
||||||
FontSize="17"
|
FontSize="17"
|
||||||
IsVisible="{Binding !IsSelected}" />
|
IsVisible="{Binding !IsSelected}" />
|
||||||
<fi:FluentIcon Icon="{Binding Icon}"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Filled"
|
Text="{Binding IconGlyph}"
|
||||||
Foreground="{DynamicResource InstallerTextPrimaryBrush}"
|
Foreground="{DynamicResource InstallerTextPrimaryBrush}"
|
||||||
FontSize="17"
|
FontSize="17"
|
||||||
IsVisible="{Binding IsSelected}" />
|
IsVisible="{Binding IsSelected}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid Grid.Column="1"
|
<Grid Grid.Column="1"
|
||||||
VerticalAlignment="Center">
|
VerticalAlignment="Center">
|
||||||
@@ -257,9 +256,9 @@
|
|||||||
Height="40"
|
Height="40"
|
||||||
Background="{DynamicResource InstallerSubtleFillBrush}"
|
Background="{DynamicResource InstallerSubtleFillBrush}"
|
||||||
CornerRadius="{DynamicResource DesignCornerRadiusMd}">
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}">
|
||||||
<fi:FluentIcon Icon="CloudArrowDown"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text=""
|
||||||
FontSize="20" />
|
FontSize="20" />
|
||||||
</Border>
|
</Border>
|
||||||
<StackPanel Grid.Column="1"
|
<StackPanel Grid.Column="1"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
@@ -302,8 +301,8 @@
|
|||||||
Command="{Binding BrowseCommand}">
|
Command="{Binding BrowseCommand}">
|
||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
<fi:FluentIcon Icon="FolderOpen"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular" />
|
Text="" />
|
||||||
<TextBlock Text="浏览" />
|
<TextBlock Text="浏览" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -341,10 +340,10 @@
|
|||||||
<Border Classes="info-panel">
|
<Border Classes="info-panel">
|
||||||
<Grid ColumnDefinitions="Auto,*"
|
<Grid ColumnDefinitions="Auto,*"
|
||||||
ColumnSpacing="10">
|
ColumnSpacing="10">
|
||||||
<fi:FluentIcon Icon="Shield"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text=""
|
||||||
Foreground="{DynamicResource InstallerAccentBrush}"
|
Foreground="{DynamicResource InstallerAccentBrush}"
|
||||||
FontSize="18" />
|
FontSize="18" />
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Text="安装器会发送匿名设备码、系统与架构信息、目标版本和请求 IP;不会上传用户名、机器名或安装目录。"
|
Text="安装器会发送匿名设备码、系统与架构信息、目标版本和请求 IP;不会上传用户名、机器名或安装目录。"
|
||||||
Classes="muted" />
|
Classes="muted" />
|
||||||
@@ -416,8 +415,8 @@
|
|||||||
Command="{Binding StartInstallCommand}">
|
Command="{Binding StartInstallCommand}">
|
||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
<fi:FluentIcon Icon="ArrowDownload"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular" />
|
Text="" />
|
||||||
<TextBlock Text="开始安装" />
|
<TextBlock Text="开始安装" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -426,8 +425,8 @@
|
|||||||
IsEnabled="{Binding IsInstalling}">
|
IsEnabled="{Binding IsInstalling}">
|
||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
<fi:FluentIcon Icon="Dismiss"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular" />
|
Text="" />
|
||||||
<TextBlock Text="取消" />
|
<TextBlock Text="取消" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -454,10 +453,10 @@
|
|||||||
Height="40"
|
Height="40"
|
||||||
Background="{DynamicResource InstallerSubtleFillBrush}"
|
Background="{DynamicResource InstallerSubtleFillBrush}"
|
||||||
CornerRadius="{DynamicResource DesignCornerRadiusMd}">
|
CornerRadius="{DynamicResource DesignCornerRadiusMd}">
|
||||||
<fi:FluentIcon Icon="CheckmarkCircle"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text=""
|
||||||
Foreground="{DynamicResource InstallerSuccessBrush}"
|
Foreground="{DynamicResource InstallerSuccessBrush}"
|
||||||
FontSize="22" />
|
FontSize="22" />
|
||||||
</Border>
|
</Border>
|
||||||
<StackPanel Grid.Column="1"
|
<StackPanel Grid.Column="1"
|
||||||
Spacing="12">
|
Spacing="12">
|
||||||
@@ -473,8 +472,8 @@
|
|||||||
Command="{Binding LaunchCommand}">
|
Command="{Binding LaunchCommand}">
|
||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
<fi:FluentIcon Icon="Play"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular" />
|
Text="" />
|
||||||
<TextBlock Text="打开阑山桌面" />
|
<TextBlock Text="打开阑山桌面" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -495,10 +494,10 @@
|
|||||||
IsVisible="{Binding HasError}">
|
IsVisible="{Binding HasError}">
|
||||||
<Grid ColumnDefinitions="Auto,*"
|
<Grid ColumnDefinitions="Auto,*"
|
||||||
ColumnSpacing="10">
|
ColumnSpacing="10">
|
||||||
<fi:FluentIcon Icon="ErrorCircle"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular"
|
Text=""
|
||||||
Foreground="{DynamicResource InstallerErrorBrush}"
|
Foreground="{DynamicResource InstallerErrorBrush}"
|
||||||
FontSize="18" />
|
FontSize="18" />
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Text="{Binding ErrorMessage}"
|
Text="{Binding ErrorMessage}"
|
||||||
Foreground="{DynamicResource InstallerErrorBrush}"
|
Foreground="{DynamicResource InstallerErrorBrush}"
|
||||||
@@ -511,8 +510,8 @@
|
|||||||
Command="{Binding BackCommand}">
|
Command="{Binding BackCommand}">
|
||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
<fi:FluentIcon Icon="ArrowLeft"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular" />
|
Text="" />
|
||||||
<TextBlock Text="上一步" />
|
<TextBlock Text="上一步" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -522,8 +521,8 @@
|
|||||||
<StackPanel Orientation="Horizontal"
|
<StackPanel Orientation="Horizontal"
|
||||||
Spacing="6">
|
Spacing="6">
|
||||||
<TextBlock Text="下一步" />
|
<TextBlock Text="下一步" />
|
||||||
<fi:FluentIcon Icon="ArrowRight"
|
<TextBlock Classes="installer-icon"
|
||||||
IconVariant="Regular" />
|
Text="" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
</requestedPrivileges>
|
</requestedPrivileges>
|
||||||
</security>
|
</security>
|
||||||
</trustInfo>
|
</trustInfo>
|
||||||
|
|||||||
@@ -212,6 +212,33 @@ public sealed class OnlineInstallerCoreTests : IDisposable
|
|||||||
Assert.ThrowsAny<Exception>(() => InstallerPathGuard.NormalizeInstallPath(path));
|
Assert.ThrowsAny<Exception>(() => InstallerPathGuard.NormalizeInstallPath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InstallerPathGuard_DefaultsToUserWritableProgramsFolder()
|
||||||
|
{
|
||||||
|
var path = InstallerPathGuard.GetDefaultInstallPath();
|
||||||
|
|
||||||
|
Assert.EndsWith(Path.Combine("Programs", InstallerPathGuard.ApplicationDirectoryName), path);
|
||||||
|
Assert.DoesNotContain("Program Files", path, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InstallerElevation_DetectsProtectedProgramFilesPath()
|
||||||
|
{
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||||
|
if (string.IsNullOrWhiteSpace(programFiles))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(InstallerElevation.RequiresElevation(Path.Combine(programFiles, InstallerPathGuard.ApplicationDirectoryName)));
|
||||||
|
Assert.False(InstallerElevation.RequiresElevation(Path.Combine(_tempRoot, InstallerPathGuard.ApplicationDirectoryName)));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task FilesPackageInstaller_DeploysFullPackageWithCurrentMarker()
|
public async Task FilesPackageInstaller_DeploysFullPackageWithCurrentMarker()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -116,8 +116,8 @@ public sealed class ComponentRegistry
|
|||||||
"Class Schedule",
|
"Class Schedule",
|
||||||
"CalendarDate",
|
"CalendarDate",
|
||||||
"Date",
|
"Date",
|
||||||
MinWidthCells: 2,
|
MinWidthCells: 4,
|
||||||
MinHeightCells: 4,
|
MinHeightCells: 3,
|
||||||
AllowStatusBarPlacement: false,
|
AllowStatusBarPlacement: false,
|
||||||
AllowDesktopPlacement: true,
|
AllowDesktopPlacement: true,
|
||||||
ResizeMode: DesktopComponentResizeMode.Free),
|
ResizeMode: DesktopComponentResizeMode.Free),
|
||||||
|
|||||||
@@ -683,18 +683,18 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
var scale = ResolveScale();
|
var scale = ResolveScale();
|
||||||
var cardRadius = ComponentChromeCornerRadiusHelper.Small();
|
var cardRadius = ComponentChromeCornerRadiusHelper.Small();
|
||||||
var timeFontSize = Math.Clamp(11 * scale, 8, 14);
|
var timeFontSize = Math.Clamp(16 * scale, 12, 22);
|
||||||
var courseNameFontSize = Math.Clamp(14 * scale, 10, 18);
|
var courseNameFontSize = Math.Clamp(20 * scale, 16, 28);
|
||||||
var detailFontSize = Math.Clamp(11 * scale, 8, 14);
|
var detailFontSize = Math.Clamp(15 * scale, 12, 20);
|
||||||
var progressFontSize = Math.Clamp(10 * scale, 7, 12);
|
var progressFontSize = Math.Clamp(13 * scale, 10, 16);
|
||||||
var cardPadding = new Thickness(
|
var cardPadding = new Thickness(
|
||||||
Math.Clamp(10 * scale, 6, 14),
|
Math.Clamp(14 * scale, 10, 20),
|
||||||
Math.Clamp(8 * scale, 5, 12),
|
Math.Clamp(12 * scale, 8, 18),
|
||||||
Math.Clamp(10 * scale, 6, 14),
|
Math.Clamp(14 * scale, 10, 20),
|
||||||
Math.Clamp(8 * scale, 5, 12));
|
Math.Clamp(12 * scale, 8, 18));
|
||||||
var timeColumnWidth = Math.Clamp(44 * scale, 30, 56);
|
var timeColumnWidth = Math.Clamp(60 * scale, 45, 80);
|
||||||
var accentBarWidth = Math.Clamp(3 * scale, 2, 4);
|
var accentBarWidth = Math.Clamp(4 * scale, 3, 6);
|
||||||
var progressBarHeight = Math.Clamp(3 * scale, 2, 4);
|
var progressBarHeight = Math.Clamp(4 * scale, 3, 6);
|
||||||
|
|
||||||
for (var i = 0; i < _courseItems.Count; i++)
|
for (var i = 0; i < _courseItems.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -894,10 +894,10 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
|
|||||||
var itemBorder = new Border
|
var itemBorder = new Border
|
||||||
{
|
{
|
||||||
Padding = new Thickness(
|
Padding = new Thickness(
|
||||||
Math.Clamp(10 * scale, 6, 14),
|
Math.Clamp(14 * scale, 10, 20),
|
||||||
Math.Clamp(2 * scale, 1, 4),
|
Math.Clamp(4 * scale, 2, 6),
|
||||||
Math.Clamp(10 * scale, 6, 14),
|
Math.Clamp(14 * scale, 10, 20),
|
||||||
Math.Clamp(2 * scale, 1, 4)),
|
Math.Clamp(4 * scale, 2, 6)),
|
||||||
Background = Brushes.Transparent,
|
Background = Brushes.Transparent,
|
||||||
Child = itemGrid
|
Child = itemGrid
|
||||||
};
|
};
|
||||||
@@ -1022,11 +1022,11 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
|
|||||||
|
|
||||||
HeaderGrid.ColumnSpacing = Math.Clamp(8 * scale, 4, 14);
|
HeaderGrid.ColumnSpacing = Math.Clamp(8 * scale, 4, 14);
|
||||||
DateGroup.Spacing = Math.Clamp(1.5 * scale, 0.5, 3);
|
DateGroup.Spacing = Math.Clamp(1.5 * scale, 0.5, 3);
|
||||||
CourseListPanel.Spacing = Math.Clamp(2 * scale, 0, 6);
|
CourseListPanel.Spacing = Math.Clamp(4 * scale, 2, 8);
|
||||||
|
|
||||||
var dateFontByScale = Math.Clamp(28 * scale, 14, 36);
|
var dateFontByScale = Math.Clamp(36 * scale, 20, 48);
|
||||||
var weekdayFontByScale = Math.Clamp(14 * scale, 10, 18);
|
var weekdayFontByScale = Math.Clamp(18 * scale, 14, 24);
|
||||||
var classCountFontByScale = Math.Clamp(12 * scale, 9, 15);
|
var classCountFontByScale = Math.Clamp(15 * scale, 12, 20);
|
||||||
|
|
||||||
var availableWidth = Math.Max(1, Bounds.Width - headerPadding.Left - headerPadding.Right);
|
var availableWidth = Math.Max(1, Bounds.Width - headerPadding.Left - headerPadding.Right);
|
||||||
var dateGroupEstimatedWidth = dateFontByScale * 0.6 * 3 + DateGroup.Spacing * 2;
|
var dateGroupEstimatedWidth = dateFontByScale * 0.6 * 3 + DateGroup.Spacing * 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user