Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to Reentry are documented here. The format follows

## [Unreleased]

- HUD: call `AppWindow.SetIcon` on the main, settings, and consent windows (the caption does not pick up `ApplicationIcon`). Replace the 195-byte PNG-in-ICO with a 16/32/48 BMP glyph derived from the existing teal mark.
- HUD: update restore/startup rows in place — `Sync` no longer `Clear()`s bound collections on the 1 Hz tick (that emptied both lists and reset subsection scroll). Footer elapsed still ticks every second.
- HUD: session progress bar plus “N / M settled”, compact single-line rows, and colored status chips (Interactive green, Pending/Starting amber, Failed purple, Hung orange, Disabled gray). Per-row clocks that duplicated the footer are gone.

- Launch on Windows 11 25H2: use the installed WASDK 2.4 runtime instead of the self-contained CoreMessagingXP payload (0xC0000602). Give the tray icon a generated glyph so ForceCreate has an IconSource.

## [0.1.0-alpha1] - 2026-08-20
Expand Down
Binary file modified src/Reentry.App/Assets/reentry.ico
Binary file not shown.
1 change: 1 addition & 0 deletions src/Reentry.App/ConsentWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public ConsentWindow()
InitializeComponent();
Title = "Reentry";
SystemBackdrop = new Microsoft.UI.Xaml.Media.MicaBackdrop();
WindowIcon.Apply(this);
Closed += (_, _) => _tcs.TrySetResult(false);
}

Expand Down
132 changes: 101 additions & 31 deletions src/Reentry.App/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@
xmlns:vm="using:Reentry.App.ViewModels"
Title="Reentry">

<Grid Padding="16" RowSpacing="12">
<Grid Padding="16" RowSpacing="8">
<Grid.Resources>
<Style x:Key="HudRowItem" TargetType="ListViewItem">
<Setter Property="MinHeight" Value="26"/>
<Setter Property="Padding" Value="6,1"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
Expand All @@ -21,60 +30,121 @@
<TextBlock Text="{x:Bind ViewModel.BootDetail, Mode=OneWay}" Opacity="0.75" TextWrapping="Wrap"/>
</StackPanel>

<TextBlock Grid.Row="1" Text="Session restore (inferred)" FontWeight="SemiBold" Margin="0,8,0,0"/>
<ListView Grid.Row="2" ItemsSource="{x:Bind ViewModel.RestoreRows, Mode=OneWay}" SelectionMode="None">
<Grid Grid.Row="1" ColumnSpacing="10" Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ProgressBar Minimum="0" Maximum="1"
Value="{x:Bind ViewModel.SettledFraction, Mode=OneWay}"
Height="4"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{x:Bind ViewModel.SettledSummary, Mode=OneWay}"
FontSize="12"
Opacity="0.8"
VerticalAlignment="Center"/>
</Grid>

<TextBlock Grid.Row="2" Text="Session restore (inferred)" FontWeight="SemiBold" Margin="0,4,0,0"/>
<ListView Grid.Row="3"
ItemsSource="{x:Bind ViewModel.RestoreRows, Mode=OneWay}"
ItemContainerStyle="{StaticResource HudRowItem}"
SelectionMode="None"
IsItemClickEnabled="False">
<ListView.ItemContainerTransitions>
<TransitionCollection/>
</ListView.ItemContainerTransitions>
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:TrackedAppRow">
<Grid ColumnSpacing="8">
<Grid ColumnSpacing="8" MinHeight="22">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="72"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind StateGlyph, Mode=OneWay}" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1">
<TextBlock Text="{x:Bind Name, Mode=OneWay}"/>
<TextBlock Text="Reentry-managed" Opacity="0.7" FontSize="11" Visibility="{x:Bind ManagedVisibility, Mode=OneWay}"/>
</StackPanel>
<TextBlock Grid.Column="2" Text="{x:Bind Source, Mode=OneWay}" Opacity="0.7" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" Text="{x:Bind State, Mode=OneWay}" VerticalAlignment="Center"/>
<TextBlock Grid.Column="4" Text="{x:Bind Elapsed, Mode=OneWay}" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBlock Text="{x:Bind Name, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"/>
<Border Grid.Column="1"
Background="#332DB4A0"
Padding="5,1"
CornerRadius="3"
Visibility="{x:Bind ManagedVisibility, Mode=OneWay}"
VerticalAlignment="Center">
<TextBlock Text="managed" FontSize="10" Opacity="0.9"/>
</Border>
<TextBlock Grid.Column="2"
Text="{x:Bind Source, Mode=OneWay}"
Opacity="0.6"
FontSize="12"
VerticalAlignment="Center"/>
<Border Grid.Column="3"
Background="{x:Bind StateBrush, Mode=OneWay}"
Padding="6,1"
CornerRadius="3"
VerticalAlignment="Center">
<TextBlock Text="{x:Bind State, Mode=OneWay}"
Foreground="{x:Bind ChipForeground, Mode=OneWay}"
FontSize="11"
FontWeight="SemiBold"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

<TextBlock Grid.Row="3" Text="Startup apps" FontWeight="SemiBold" Margin="0,8,0,0"/>
<ListView Grid.Row="4" ItemsSource="{x:Bind ViewModel.StartupRows, Mode=OneWay}" SelectionMode="None">
<TextBlock Grid.Row="4" Text="Startup apps" FontWeight="SemiBold" Margin="0,4,0,0"/>
<ListView Grid.Row="5"
ItemsSource="{x:Bind ViewModel.StartupRows, Mode=OneWay}"
ItemContainerStyle="{StaticResource HudRowItem}"
SelectionMode="None"
IsItemClickEnabled="False">
<ListView.ItemContainerTransitions>
<TransitionCollection/>
</ListView.ItemContainerTransitions>
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:TrackedAppRow">
<Grid ColumnSpacing="8">
<Grid ColumnSpacing="8" MinHeight="22">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="72"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind StateGlyph, Mode=OneWay}" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1">
<TextBlock Text="{x:Bind Name, Mode=OneWay}"/>
<Border Background="#223D8B40" Padding="6,1" CornerRadius="4"
Visibility="{x:Bind ManagedVisibility, Mode=OneWay}" HorizontalAlignment="Left">
<TextBlock Text="Reentry-managed" FontSize="11"/>
</Border>
</StackPanel>
<TextBlock Grid.Column="2" Text="{x:Bind Source, Mode=OneWay}" Opacity="0.7" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" Text="{x:Bind State, Mode=OneWay}" VerticalAlignment="Center"/>
<TextBlock Grid.Column="4" Text="{x:Bind Elapsed, Mode=OneWay}" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBlock Text="{x:Bind Name, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"/>
<Border Grid.Column="1"
Background="#332DB4A0"
Padding="5,1"
CornerRadius="3"
Visibility="{x:Bind ManagedVisibility, Mode=OneWay}"
VerticalAlignment="Center">
<TextBlock Text="managed" FontSize="10" Opacity="0.9"/>
</Border>
<TextBlock Grid.Column="2"
Text="{x:Bind Source, Mode=OneWay}"
Opacity="0.6"
FontSize="12"
VerticalAlignment="Center"/>
<Border Grid.Column="3"
Background="{x:Bind StateBrush, Mode=OneWay}"
Padding="6,1"
CornerRadius="3"
VerticalAlignment="Center">
<TextBlock Text="{x:Bind State, Mode=OneWay}"
Foreground="{x:Bind ChipForeground, Mode=OneWay}"
FontSize="11"
FontWeight="SemiBold"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

<Grid Grid.Row="5">
<Grid Grid.Row="6">
<TextBlock Text="Session elapsed" Opacity="0.7" VerticalAlignment="Center"/>
<TextBlock Text="{x:Bind ViewModel.FooterElapsed, Mode=OneWay}" HorizontalAlignment="Right" FontWeight="SemiBold"/>
</Grid>
Expand Down
5 changes: 1 addition & 4 deletions src/Reentry.App/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Reentry.App.ViewModels;
Expand All @@ -15,9 +14,7 @@ public MainWindow(HudViewModel viewModel)
Title = "Reentry";
SystemBackdrop = new Microsoft.UI.Xaml.Media.MicaBackdrop();

var hwnd = WindowNative.GetWindowHandle(this);
var id = Win32Interop.GetWindowIdFromWindow(hwnd);
var appWindow = AppWindow.GetFromWindowId(id);
var appWindow = WindowIcon.Apply(this);
appWindow.Resize(new Windows.Graphics.SizeInt32(540, 760));
if (appWindow.Presenter is OverlappedPresenter presenter)
{
Expand Down
1 change: 1 addition & 0 deletions src/Reentry.App/SettingsWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public SettingsWindow(SettingsViewModel viewModel)
InitializeComponent();
Title = "Reentry settings";
SystemBackdrop = new Microsoft.UI.Xaml.Media.MicaBackdrop();
WindowIcon.Apply(this);
}

public SettingsViewModel ViewModel { get; }
Expand Down
31 changes: 17 additions & 14 deletions src/Reentry.App/ViewModels/HudViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using Reentry.Core;
using Reentry.Core.Models;

namespace Reentry.App.ViewModels;

public sealed partial class HudViewModel : ObservableObject
{
[ObservableProperty] private string _bootBanner = "";

Check warning on line 10 in src/Reentry.App/ViewModels/HudViewModel.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The field Reentry.App.ViewModels.HudViewModel._bootBanner using
[ObservableProperty] private string _bootDetail = "";

Check warning on line 11 in src/Reentry.App/ViewModels/HudViewModel.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The field Reentry.App.ViewModels.HudViewModel._bootDetail using
[ObservableProperty] private string _footerElapsed = "00:00";

Check warning on line 12 in src/Reentry.App/ViewModels/HudViewModel.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The field Reentry.App.ViewModels.HudViewModel._footerElapsed using
[ObservableProperty] private DateTimeOffset _sessionStartedUtc = DateTimeOffset.UtcNow;

Check warning on line 13 in src/Reentry.App/ViewModels/HudViewModel.cs

View workflow job for this annotation

GitHub Actions / build-and-test

The field Reentry.App.ViewModels.HudViewModel._sessionStartedUtc using
[ObservableProperty] private int _settledCount;
[ObservableProperty] private int _totalCount;
[ObservableProperty] private double _settledFraction;
[ObservableProperty] private string _settledSummary = "0 / 0 settled";

public HudViewModel(BootKind bootKind)
{
Expand All @@ -30,25 +35,23 @@
var startup = apps.Where(a => a.Source is not AppSource.Arr and not AppSource.Explorer).ToList();
Sync(RestoreRows, restore);
Sync(StartupRows, startup);

TotalCount = apps.Count;
SettledCount = apps.Count(a => a.State.IsSettled());
SettledFraction = TotalCount == 0 ? 0 : (double)SettledCount / TotalCount;
SettledSummary = $"{SettledCount} / {TotalCount} settled";
FooterElapsed = Format(DateTimeOffset.UtcNow - SessionStartedUtc);
}

private static void Sync(ObservableCollection<TrackedAppRow> target, List<TrackedApp> source)
{
var byId = target.ToDictionary(r => r.Id);
target.Clear();
foreach (var app in source)
{
if (byId.TryGetValue(app.Id, out var existing))
{
existing.Apply(app);
target.Add(existing);
}
else
{
target.Add(TrackedAppRow.From(app));
}
}
CollectionSync.InPlace(
target,
source,
itemKey: r => r.Id,
sourceKey: a => a.Id,
apply: (row, app) => row.Apply(app),
create: TrackedAppRow.From);
}

private static string Format(TimeSpan elapsed)
Expand Down
23 changes: 23 additions & 0 deletions src/Reentry.App/ViewModels/TrackedAppRow.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using Reentry.Core.Models;
using Windows.UI;

namespace Reentry.App.ViewModels;

public sealed partial class TrackedAppRow : ObservableObject
{
private static readonly SolidColorBrush InteractiveBrush = Brush(16, 124, 16);
private static readonly SolidColorBrush PendingBrush = Brush(201, 156, 0);
private static readonly SolidColorBrush FailedBrush = Brush(155, 27, 90);
private static readonly SolidColorBrush HungBrush = Brush(230, 126, 34);
private static readonly SolidColorBrush DisabledBrush = Brush(107, 107, 107);
private static readonly SolidColorBrush ChipOnDark = Brush(255, 255, 255);
private static readonly SolidColorBrush ChipOnAmber = Brush(26, 18, 0);

[ObservableProperty] private string _name = "";
[ObservableProperty] private string _source = "";
[ObservableProperty] private string _state = "";
[ObservableProperty] private string _elapsed = "";
[ObservableProperty] private bool _isManaged;
[ObservableProperty] private string _stateGlyph = "●";
[ObservableProperty] private Brush _stateBrush = DisabledBrush;
[ObservableProperty] private Brush _chipForeground = ChipOnDark;

public string Id { get; init; } = "";
public Visibility ManagedVisibility => IsManaged ? Visibility.Visible : Visibility.Collapsed;
Expand Down Expand Up @@ -41,8 +53,19 @@ public void Apply(TrackedApp app)
AppState.Disabled => "–",
_ => "●",
};
(StateBrush, ChipForeground) = app.State switch
{
AppState.Interactive => ((Brush)InteractiveBrush, ChipOnDark),
AppState.Starting or AppState.Pending => (PendingBrush, ChipOnAmber),
AppState.Failed => (FailedBrush, ChipOnDark),
AppState.Hung => (HungBrush, ChipOnDark),
_ => (DisabledBrush, ChipOnDark),
};
}

private static SolidColorBrush Brush(byte r, byte g, byte b) =>
new(Color.FromArgb(255, r, g, b));

private static string FormatElapsed(TimeSpan elapsed)
{
if (elapsed.TotalHours >= 1)
Expand Down
51 changes: 51 additions & 0 deletions src/Reentry.App/WindowIcon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using WinRT.Interop;

namespace Reentry.App;

/// <summary>
/// WinUI's <see cref="Window"/> does not pick up <c>ApplicationIcon</c> for the
/// caption. Every window calls <see cref="AppWindow.SetIcon"/> with the same ICO
/// so the HUD, settings, and consent title bars match the exe/taskbar glyph.
/// </summary>
internal static class WindowIcon
{
internal const string FileName = "reentry.ico";

public static AppWindow Apply(Window window)
{
ArgumentNullException.ThrowIfNull(window);
var hwnd = WindowNative.GetWindowHandle(window);
var id = Win32Interop.GetWindowIdFromWindow(hwnd);
var appWindow = AppWindow.GetFromWindowId(id);
var path = ResolvePath();
if (path is not null)
appWindow.SetIcon(path);
return appWindow;
}

internal static string? ResolvePath()
{
var bases = new[]
{
AppContext.BaseDirectory,
Path.GetDirectoryName(Environment.ProcessPath) ?? "",
};

foreach (var root in bases)
{
if (string.IsNullOrWhiteSpace(root))
continue;
foreach (var relative in new[] { Path.Combine("Assets", FileName), FileName })
{
var candidate = Path.GetFullPath(Path.Combine(root, relative));
if (File.Exists(candidate))
return candidate;
}
}

return null;
}
}
Loading
Loading