changed.更了好多

This commit is contained in:
lincube
2026-05-12 16:46:49 +08:00
parent 563f12caa1
commit 33c264f6dd
127 changed files with 5257 additions and 10534 deletions

View File

@@ -3,16 +3,20 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Launcher.Views.ErrorWindow"
x:DataType="views:ErrorWindow"
Title="LanMountain Desktop"
Width="560"
Height="320"
Width="760"
Height="460"
MinWidth="640"
MinHeight="420"
CanResize="False"
WindowStartupLocation="CenterScreen"
Background="#111318"
TransparencyLevelHint="None"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="Mica, AcrylicBlur, None"
Icon="/Assets/logo.ico">
<Design.DataContext>
<views:ErrorWindow />
@@ -20,79 +24,128 @@
<Grid RowDefinitions="*,Auto">
<Grid Grid.Row="0"
Margin="24"
Margin="28,24,28,20"
RowDefinitions="Auto,Auto,*"
ColumnDefinitions="Auto,*">
<Border x:Name="ErrorIconBorder"
Grid.Column="0"
Width="52"
Height="52"
Margin="0,4,18,0"
Background="#2B161A"
CornerRadius="26"
Width="56"
Height="56"
Margin="0,0,18,0"
Background="{DynamicResource SystemFillColorCriticalBackgroundBrush}"
CornerRadius="28"
VerticalAlignment="Top">
<TextBlock Text="!"
FontSize="24"
FontWeight="Bold"
Foreground="#FFB4AB"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<fi:SymbolIcon Symbol="ErrorCircle"
IconVariant="Regular"
FontSize="28"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1"
Spacing="10">
Spacing="8">
<TextBlock x:Name="TitleText"
Text="Launcher could not confirm startup"
FontSize="20"
FontSize="22"
FontWeight="SemiBold"
Foreground="#F6F7FB"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock x:Name="ErrorMessageText"
Text="LanMountain Desktop did not reach the expected startup state."
FontSize="14"
Foreground="#D2D7E1"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
LineHeight="22" />
<TextBlock x:Name="SuggestionText"
Text="You can inspect logs, retry when the old process is gone, or reactivate the current instance."
FontSize="13"
Foreground="#9BA5B7"
TextWrapping="Wrap"
LineHeight="20" />
</StackPanel>
<ui:FAInfoBar x:Name="SuggestionInfoBar"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,20,0,14"
IsOpen="True"
IsClosable="False"
Severity="Warning"
Title="Startup recovery"
Message="You can inspect logs, wait for the current process, or activate the running desktop instance.">
<ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource>
</ui:FAInfoBar>
<Expander Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Header="Diagnostic details"
IsExpanded="True">
<TextBox x:Name="ErrorDetailsTextBox"
Margin="0,10,0,0"
MinHeight="150"
MaxHeight="190"
AcceptsReturn="True"
TextWrapping="Wrap"
IsReadOnly="True"
BorderThickness="0"
FontSize="12"
Text="Stage: launch&#x0a;Code: unknown"
VerticalContentAlignment="Top" />
</Expander>
</Grid>
<Border Grid.Row="1"
Padding="24,16"
Background="#171A21">
<Grid ColumnDefinitions="*,Auto,Auto,Auto"
ColumnSpacing="8">
<Button x:Name="OpenLogButton"
Grid.Column="0"
Content="Open Logs"
MinWidth="108"
Height="34"
HorizontalAlignment="Left" />
Padding="18,14"
Background="{DynamicResource LayerOnMicaBaseAltFillColorDefaultBrush}">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="12">
<StackPanel Grid.Column="0"
Orientation="Horizontal"
Spacing="8">
<Button x:Name="OpenLogButton"
MinWidth="112"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="FolderOpen" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="Open Logs"/>
</StackPanel>
</Button>
<Button x:Name="SecondaryActionButton"
Grid.Column="1"
Content="Wait"
MinWidth="108"
Height="34"
IsVisible="False" />
<Button x:Name="CopyDetailsButton"
MinWidth="100"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Copy" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="Copy"/>
</StackPanel>
</Button>
</StackPanel>
<Button x:Name="ExitButton"
Grid.Column="2"
Content="Exit"
MinWidth="90"
Height="34" />
<StackPanel Grid.Column="2"
Orientation="Horizontal"
Spacing="8">
<Button x:Name="SecondaryActionButton"
Content="Wait"
MinWidth="96"
Height="34"
IsVisible="False" />
<Button x:Name="PrimaryActionButton"
Grid.Column="3"
Content="Retry"
MinWidth="108"
Height="34" />
<Button x:Name="ExitButton"
MinWidth="92"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Dismiss" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="Exit"/>
</StackPanel>
</Button>
<Button x:Name="PrimaryActionButton"
Classes="accent"
Content="Retry"
MinWidth="112"
Height="34" />
</StackPanel>
</Grid>
</Border>
</Grid>

View File

@@ -1,8 +1,10 @@
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Launcher.Services;
namespace LanMountainDesktop.Launcher.Views;
@@ -33,9 +35,21 @@ public partial class ErrorWindow : Window
public void SetErrorMessage(string message)
{
var normalizedMessage = string.IsNullOrWhiteSpace(message)
? "LanMountain Desktop did not reach the expected startup state."
: message.Trim();
var firstLine = normalizedMessage
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault() ?? normalizedMessage;
if (this.FindControl<TextBlock>("ErrorMessageText") is { } errorText)
{
errorText.Text = message;
errorText.Text = firstLine;
}
if (this.FindControl<TextBox>("ErrorDetailsTextBox") is { } detailsTextBox)
{
detailsTextBox.Text = normalizedMessage;
}
}
@@ -120,6 +134,11 @@ public partial class ErrorWindow : Window
{
openLogButton.Click += OnOpenLogClick;
}
if (this.FindControl<Button>("CopyDetailsButton") is { } copyDetailsButton)
{
copyDetailsButton.Click += OnCopyDetailsClick;
}
}
private void ApplyActionLayout(
@@ -138,9 +157,9 @@ public partial class ErrorWindow : Window
titleText.Text = title;
}
if (this.FindControl<TextBlock>("SuggestionText") is { } suggestionText)
if (this.FindControl<FAInfoBar>("SuggestionInfoBar") is { } suggestionInfoBar)
{
suggestionText.Text = suggestion;
suggestionInfoBar.Message = suggestion;
}
if (this.FindControl<Button>("PrimaryActionButton") is { } primaryButton)
@@ -243,6 +262,28 @@ public partial class ErrorWindow : Window
await Task.CompletedTask;
}
private async void OnCopyDetailsClick(object? sender, RoutedEventArgs e)
{
try
{
var details = this.FindControl<TextBox>("ErrorDetailsTextBox")?.Text;
if (string.IsNullOrWhiteSpace(details))
{
details = this.FindControl<TextBlock>("ErrorMessageText")?.Text;
}
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard is not null && !string.IsNullOrWhiteSpace(details))
{
await clipboard.SetTextAsync(details);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[ErrorWindow] Failed to copy diagnostics: {ex}");
}
}
private void ScanDevPaths()
{
var executable = OperatingSystem.IsWindows() ? "LanMountainDesktop.exe" : "LanMountainDesktop";

View File

@@ -0,0 +1,124 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:LanMountainDesktop.Launcher.Views"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
mc:Ignorable="d"
x:Class="LanMountainDesktop.Launcher.Views.MultiInstancePromptWindow"
x:DataType="views:MultiInstancePromptWindow"
Title="LanMountain Desktop"
Width="620"
Height="360"
MinWidth="560"
MinHeight="330"
CanResize="False"
WindowStartupLocation="CenterScreen"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
TransparencyLevelHint="Mica, AcrylicBlur, None"
Icon="/Assets/logo.ico">
<Design.DataContext>
<views:MultiInstancePromptWindow />
</Design.DataContext>
<Grid RowDefinitions="*,Auto">
<Grid Grid.Row="0"
Margin="28,26,28,20"
RowDefinitions="Auto,Auto,*"
ColumnDefinitions="Auto,*">
<Border Grid.Column="0"
Width="52"
Height="52"
Margin="0,0,18,0"
Background="{DynamicResource SystemFillColorAttentionBackgroundBrush}"
CornerRadius="26"
VerticalAlignment="Top">
<fi:SymbolIcon Symbol="Desktop"
IconVariant="Regular"
FontSize="26"
Foreground="{DynamicResource SystemFillColorAttentionBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1"
Spacing="8">
<TextBlock Text="LanMountain Desktop is already running"
FontSize="22"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap" />
<TextBlock x:Name="MessageText"
Text="Launcher found an existing desktop instance and did not start another process."
FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
LineHeight="22" />
</StackPanel>
<ui:FAInfoBar Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,22,0,14"
IsOpen="True"
IsClosable="False"
Severity="Informational"
Title="Repeated launch"
Message="Your current setting is to show this prompt without opening the desktop automatically.">
<ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource>
</ui:FAInfoBar>
<TextBlock x:Name="DetailsText"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="No second Host process was created." />
</Grid>
<Border Grid.Row="1"
Padding="18,14"
Background="{DynamicResource LayerOnMicaBaseAltFillColorDefaultBrush}">
<Grid ColumnDefinitions="*,Auto">
<Button x:Name="CopyDetailsButton"
Grid.Column="0"
MinWidth="104"
Height="34"
HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Copy" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="Copy"/>
</StackPanel>
</Button>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Spacing="8">
<Button x:Name="CloseButton"
MinWidth="92"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="Dismiss" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="Close"/>
</StackPanel>
</Button>
<Button x:Name="OpenDesktopButton"
Classes="accent"
MinWidth="136"
Height="34">
<StackPanel Orientation="Horizontal" Spacing="6">
<fi:SymbolIcon Symbol="ArrowRight" IconVariant="Regular" FontSize="16"/>
<TextBlock Text="Open desktop"/>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Border>
</Grid>
</Window>

View File

@@ -0,0 +1,76 @@
using Avalonia.Controls;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Launcher.Views;
public partial class MultiInstancePromptWindow : Window
{
private readonly TaskCompletionSource<MultiInstancePromptResult> _completionSource =
new(TaskCreationOptions.RunContinuationsAsynchronously);
private string _details = "LanMountain Desktop is already running.";
public MultiInstancePromptWindow()
{
AvaloniaXamlLoader.Load(this);
Loaded += OnLoaded;
Closed += (_, _) => _completionSource.TrySetResult(MultiInstancePromptResult.Close);
}
public Task<MultiInstancePromptResult> WaitForChoiceAsync() => _completionSource.Task;
public void SetDetails(int processId, string shellState)
{
_details = $"Existing host PID: {processId}\nShell state: {shellState}\nNo second Host process was created.";
if (this.FindControl<TextBlock>("DetailsText") is { } detailsText)
{
detailsText.Text = _details;
}
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
if (this.FindControl<Button>("CloseButton") is { } closeButton)
{
closeButton.Click += (_, _) => Complete(MultiInstancePromptResult.Close);
}
if (this.FindControl<Button>("OpenDesktopButton") is { } openDesktopButton)
{
openDesktopButton.Click += (_, _) => Complete(MultiInstancePromptResult.OpenDesktop);
}
if (this.FindControl<Button>("CopyDetailsButton") is { } copyDetailsButton)
{
copyDetailsButton.Click += OnCopyDetailsClick;
}
}
private void Complete(MultiInstancePromptResult result)
{
_completionSource.TrySetResult(result);
Close();
}
private async void OnCopyDetailsClick(object? sender, RoutedEventArgs e)
{
try
{
if (TopLevel.GetTopLevel(this)?.Clipboard is IClipboard clipboard)
{
await clipboard.SetTextAsync(_details);
}
}
catch
{
}
}
}
public enum MultiInstancePromptResult
{
Close,
OpenDesktop
}