ChangelogHtml(AppCastItem item)
+ {
+ const string cssResourceName = "Coder.Desktop.App.Assets.changelog.css";
+ const string htmlTemplate = @"
+
+
+
+
+
+
+
+
+
+
+ {{CONTENT}}
+
+
+
+";
+
+ const string githubMarkdownCssToken = "{{GITHUB_MARKDOWN_CSS}}";
+ const string themeToken = "{{THEME}}";
+ const string contentToken = "{{CONTENT}}";
+
+ // We load the CSS from an embedded asset since it's large.
+ var css = "";
+ try
+ {
+ await using var stream = typeof(App).Assembly.GetManifestResourceStream(cssResourceName)
+ ?? throw new FileNotFoundException($"Embedded resource not found: {cssResourceName}");
+ using var reader = new StreamReader(stream);
+ css = await reader.ReadToEndAsync();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "failed to load changelog CSS theme from embedded asset, ignoring");
+ }
+
+ // We store the changelog in the description field, rather than using
+ // the release notes URL to avoid extra requests.
+ var innerHtml = item.Description;
+ if (string.IsNullOrWhiteSpace(innerHtml))
+ {
+ innerHtml = "No release notes available.
";
+ }
+
+ // The theme doesn't automatically update.
+ var currentTheme = Application.Current.RequestedTheme == ApplicationTheme.Dark ? "dark" : "light";
+ return htmlTemplate
+ .Replace(githubMarkdownCssToken, css)
+ .Replace(themeToken, currentTheme)
+ .Replace(contentToken, innerHtml);
+ }
+
+ public async Task Changelog_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (sender is not WebView2 webView)
+ return;
+
+ // Start the engine.
+ await webView.EnsureCoreWebView2Async();
+
+ // Disable unwanted features.
+ var settings = webView.CoreWebView2.Settings;
+ settings.IsScriptEnabled = false; // disables JS
+ settings.AreHostObjectsAllowed = false; // disables interaction with app code
+#if !DEBUG
+ settings.AreDefaultContextMenusEnabled = false; // disables right-click
+ settings.AreDevToolsEnabled = false;
+#endif
+ settings.IsZoomControlEnabled = false;
+ settings.IsStatusBarEnabled = false;
+
+ // Hijack navigation to prevent links opening in the web view.
+ webView.CoreWebView2.NavigationStarting += (_, e) =>
+ {
+ // webView.NavigateToString uses data URIs, so allow those to work.
+ if (e.Uri.StartsWith("data:text/html", StringComparison.OrdinalIgnoreCase))
+ return;
+
+ // Prevent the web view from trying to navigate to it.
+ e.Cancel = true;
+
+ // Launch HTTP or HTTPS URLs in the default browser.
+ if (Uri.TryCreate(e.Uri, UriKind.Absolute, out var uri) && uri is { Scheme: "http" or "https" })
+ Process.Start(new ProcessStartInfo(e.Uri) { UseShellExecute = true });
+ };
+ webView.CoreWebView2.NewWindowRequested += (_, e) =>
+ {
+ // Prevent new windows from being launched (e.g. target="_blank").
+ e.Handled = true;
+ // Launch HTTP or HTTPS URLs in the default browser.
+ if (Uri.TryCreate(e.Uri, UriKind.Absolute, out var uri) && uri is { Scheme: "http" or "https" })
+ Process.Start(new ProcessStartInfo(e.Uri) { UseShellExecute = true });
+ };
+
+ var html = await ChangelogHtml(CurrentItem);
+ webView.NavigateToString(html);
+ }
+
+ private void SendResponse(UpdateAvailableResult result)
+ {
+ Result = result;
+ UserResponded?.Invoke(this, new UpdateResponseEventArgs(result, CurrentItem));
+ }
+
+ public void SkipButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (!SkipButtonVisible || MissingCriticalUpdate)
+ return;
+ SendResponse(UpdateAvailableResult.SkipUpdate);
+ }
+
+ public void RemindMeLaterButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (!RemindMeLaterButtonVisible || MissingCriticalUpdate)
+ return;
+ SendResponse(UpdateAvailableResult.RemindMeLater);
+ }
+
+ public void InstallButton_Click(object sender, RoutedEventArgs e)
+ {
+ SendResponse(UpdateAvailableResult.InstallUpdate);
+ }
+}
diff --git a/App/Views/DirectoryPickerWindow.xaml b/App/Views/DirectoryPickerWindow.xaml
new file mode 100644
index 0000000..ce1623b
--- /dev/null
+++ b/App/Views/DirectoryPickerWindow.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/DirectoryPickerWindow.xaml.cs b/App/Views/DirectoryPickerWindow.xaml.cs
new file mode 100644
index 0000000..d2eb320
--- /dev/null
+++ b/App/Views/DirectoryPickerWindow.xaml.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Runtime.InteropServices;
+using Windows.Graphics;
+using Coder.Desktop.App.Utils;
+using Coder.Desktop.App.ViewModels;
+using Coder.Desktop.App.Views.Pages;
+using Microsoft.UI.Windowing;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
+using WinRT.Interop;
+using WinUIEx;
+
+namespace Coder.Desktop.App.Views;
+
+public sealed partial class DirectoryPickerWindow : WindowEx
+{
+ public DirectoryPickerWindow(DirectoryPickerViewModel viewModel)
+ {
+ InitializeComponent();
+ TitleBarIcon.SetTitlebarIcon(this);
+
+ viewModel.Initialize(this, DispatcherQueue);
+ RootFrame.Content = new DirectoryPickerMainPage(viewModel);
+
+ // This will be moved to the center of the parent window in SetParent.
+ this.CenterOnScreen();
+ }
+
+ public void SetParent(Window parentWindow)
+ {
+ // Move the window to the center of the parent window.
+ var scale = DisplayScale.WindowScale(parentWindow);
+ var windowPos = new PointInt32(
+ parentWindow.AppWindow.Position.X + parentWindow.AppWindow.Size.Width / 2 - AppWindow.Size.Width / 2,
+ parentWindow.AppWindow.Position.Y + parentWindow.AppWindow.Size.Height / 2 - AppWindow.Size.Height / 2
+ );
+
+ // Ensure we stay within the display.
+ var workArea = DisplayArea.GetFromPoint(parentWindow.AppWindow.Position, DisplayAreaFallback.Primary).WorkArea;
+ if (windowPos.X + AppWindow.Size.Width > workArea.X + workArea.Width) // right edge
+ windowPos.X = workArea.X + workArea.Width - AppWindow.Size.Width;
+ if (windowPos.Y + AppWindow.Size.Height > workArea.Y + workArea.Height) // bottom edge
+ windowPos.Y = workArea.Y + workArea.Height - AppWindow.Size.Height;
+ if (windowPos.X < workArea.X) // left edge
+ windowPos.X = workArea.X;
+ if (windowPos.Y < workArea.Y) // top edge
+ windowPos.Y = workArea.Y;
+
+ AppWindow.Move(windowPos);
+
+ var parentHandle = WindowNative.GetWindowHandle(parentWindow);
+ var thisHandle = WindowNative.GetWindowHandle(this);
+
+ // Set the parent window in win API.
+ NativeApi.SetWindowParent(thisHandle, parentHandle);
+
+ // Override the presenter, which allows us to enable modal-like
+ // behavior for this window:
+ // - Disables the parent window
+ // - Any activations of the parent window will play a bell sound and
+ // focus the modal window
+ //
+ // This behavior is very similar to the native file/directory picker on
+ // Windows.
+ var presenter = OverlappedPresenter.CreateForDialog();
+ presenter.IsModal = true;
+ AppWindow.SetPresenter(presenter);
+ AppWindow.Show();
+
+ // Cascade close events.
+ parentWindow.Closed += OnParentWindowClosed;
+ Closed += (_, _) =>
+ {
+ parentWindow.Closed -= OnParentWindowClosed;
+ parentWindow.Activate();
+ };
+ }
+
+ private void OnParentWindowClosed(object? sender, WindowEventArgs e)
+ {
+ Close();
+ }
+
+ private static class NativeApi
+ {
+ [DllImport("user32.dll")]
+ private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+
+ public static void SetWindowParent(IntPtr window, IntPtr parent)
+ {
+ SetWindowLongPtr(window, -8, parent);
+ }
+ }
+}
diff --git a/App/Views/FileSyncListWindow.xaml b/App/Views/FileSyncListWindow.xaml
index 070efd2..991d02a 100644
--- a/App/Views/FileSyncListWindow.xaml
+++ b/App/Views/FileSyncListWindow.xaml
@@ -13,7 +13,7 @@
MinWidth="1000" MinHeight="300">
-
+
diff --git a/App/Views/FileSyncListWindow.xaml.cs b/App/Views/FileSyncListWindow.xaml.cs
index 8a409d7..9d8510b 100644
--- a/App/Views/FileSyncListWindow.xaml.cs
+++ b/App/Views/FileSyncListWindow.xaml.cs
@@ -1,3 +1,4 @@
+using Coder.Desktop.App.Utils;
using Coder.Desktop.App.ViewModels;
using Coder.Desktop.App.Views.Pages;
using Microsoft.UI.Xaml.Media;
@@ -13,10 +14,10 @@ public FileSyncListWindow(FileSyncListViewModel viewModel)
{
ViewModel = viewModel;
InitializeComponent();
- SystemBackdrop = new DesktopAcrylicBackdrop();
+ TitleBarIcon.SetTitlebarIcon(this);
ViewModel.Initialize(this, DispatcherQueue);
- RootFrame.Content = new FileSyncListMainPage(ViewModel, this);
+ RootFrame.Content = new FileSyncListMainPage(ViewModel);
this.CenterOnScreen();
}
diff --git a/App/Views/MessageWindow.xaml b/App/Views/MessageWindow.xaml
new file mode 100644
index 0000000..e38ee4f
--- /dev/null
+++ b/App/Views/MessageWindow.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/MessageWindow.xaml.cs b/App/Views/MessageWindow.xaml.cs
new file mode 100644
index 0000000..00ed204
--- /dev/null
+++ b/App/Views/MessageWindow.xaml.cs
@@ -0,0 +1,32 @@
+using Coder.Desktop.App.Utils;
+using Microsoft.UI.Xaml;
+using WinUIEx;
+
+namespace Coder.Desktop.App.Views;
+
+public sealed partial class MessageWindow : WindowEx
+{
+ public string MessageTitle;
+ public string MessageContent;
+
+ public MessageWindow(string title, string content, string windowTitle = "Coder Desktop")
+ {
+ Title = windowTitle;
+ MessageTitle = title;
+ MessageContent = content;
+
+ InitializeComponent();
+ TitleBarIcon.SetTitlebarIcon(this);
+ this.CenterOnScreen();
+ AppWindow.Show();
+
+ // TODO: the window should resize to fit content and not be resizable
+ // by the user, probably possible with SizedFrame and a Page, but
+ // I didn't want to add a Page for this
+ }
+
+ public void CloseClicked(object? sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+}
diff --git a/App/Views/Pages/DirectoryPickerMainPage.xaml b/App/Views/Pages/DirectoryPickerMainPage.xaml
new file mode 100644
index 0000000..0fbbaea
--- /dev/null
+++ b/App/Views/Pages/DirectoryPickerMainPage.xaml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/Pages/DirectoryPickerMainPage.xaml.cs b/App/Views/Pages/DirectoryPickerMainPage.xaml.cs
new file mode 100644
index 0000000..4e26200
--- /dev/null
+++ b/App/Views/Pages/DirectoryPickerMainPage.xaml.cs
@@ -0,0 +1,27 @@
+using Coder.Desktop.App.ViewModels;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Coder.Desktop.App.Views.Pages;
+
+public sealed partial class DirectoryPickerMainPage : Page
+{
+ public readonly DirectoryPickerViewModel ViewModel;
+
+ public DirectoryPickerMainPage(DirectoryPickerViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ InitializeComponent();
+ }
+
+ private void TooltipText_IsTextTrimmedChanged(TextBlock sender, IsTextTrimmedChangedEventArgs e)
+ {
+ ToolTipService.SetToolTip(sender, null);
+ if (!sender.IsTextTrimmed) return;
+
+ var toolTip = new ToolTip
+ {
+ Content = sender.Text,
+ };
+ ToolTipService.SetToolTip(sender, toolTip);
+ }
+}
diff --git a/App/Views/Pages/FileSyncListMainPage.xaml b/App/Views/Pages/FileSyncListMainPage.xaml
index d38bc29..7623e4a 100644
--- a/App/Views/Pages/FileSyncListMainPage.xaml
+++ b/App/Views/Pages/FileSyncListMainPage.xaml
@@ -8,8 +8,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:Coder.Desktop.App.ViewModels"
xmlns:converters="using:Coder.Desktop.App.Converters"
- mc:Ignorable="d"
- Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+ mc:Ignorable="d">
-
-
+
+
+
+
+
+
+
+
-
+
@@ -132,7 +137,7 @@
@@ -266,7 +271,7 @@
@@ -274,8 +279,11 @@
-
-
+
+
@@ -309,12 +317,13 @@
Grid.Column="0"
Margin="0,0,5,0"
VerticalAlignment="Stretch"
+ IsEnabled="{x:Bind ViewModel.OperationInProgress,Converter={StaticResource InverseBoolConverter}, Mode=OneWay}"
Text="{x:Bind ViewModel.NewSessionLocalPath, Mode=TwoWay}" />
@@ -322,23 +331,44 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/Pages/FileSyncListMainPage.xaml.cs b/App/Views/Pages/FileSyncListMainPage.xaml.cs
index c54c29e..a677522 100644
--- a/App/Views/Pages/FileSyncListMainPage.xaml.cs
+++ b/App/Views/Pages/FileSyncListMainPage.xaml.cs
@@ -1,7 +1,4 @@
-using System.Threading.Tasks;
using Coder.Desktop.App.ViewModels;
-using CommunityToolkit.Mvvm.Input;
-using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Coder.Desktop.App.Views.Pages;
@@ -10,12 +7,9 @@ public sealed partial class FileSyncListMainPage : Page
{
public FileSyncListViewModel ViewModel;
- private readonly Window _window;
-
- public FileSyncListMainPage(FileSyncListViewModel viewModel, Window window)
+ public FileSyncListMainPage(FileSyncListViewModel viewModel)
{
ViewModel = viewModel; // already initialized
- _window = window;
InitializeComponent();
}
@@ -31,10 +25,4 @@ private void TooltipText_IsTextTrimmedChanged(TextBlock sender, IsTextTrimmedCha
};
ToolTipService.SetToolTip(sender, toolTip);
}
-
- [RelayCommand]
- public async Task OpenLocalPathSelectDialog()
- {
- await ViewModel.OpenLocalPathSelectDialog(_window);
- }
}
diff --git a/App/Views/Pages/SettingsMainPage.xaml b/App/Views/Pages/SettingsMainPage.xaml
new file mode 100644
index 0000000..36b471d
--- /dev/null
+++ b/App/Views/Pages/SettingsMainPage.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/Pages/SettingsMainPage.xaml.cs b/App/Views/Pages/SettingsMainPage.xaml.cs
new file mode 100644
index 0000000..f2494b1
--- /dev/null
+++ b/App/Views/Pages/SettingsMainPage.xaml.cs
@@ -0,0 +1,15 @@
+using Coder.Desktop.App.ViewModels;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Coder.Desktop.App.Views.Pages;
+
+public sealed partial class SettingsMainPage : Page
+{
+ public SettingsViewModel ViewModel;
+
+ public SettingsMainPage(SettingsViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ InitializeComponent();
+ }
+}
diff --git a/App/Views/Pages/SignInTokenPage.xaml b/App/Views/Pages/SignInTokenPage.xaml
index 8613f19..7f20b69 100644
--- a/App/Views/Pages/SignInTokenPage.xaml
+++ b/App/Views/Pages/SignInTokenPage.xaml
@@ -6,8 +6,7 @@
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"
- mc:Ignorable="d"
- Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+ mc:Ignorable="d">
+ Password="{x:Bind ViewModel.ApiToken, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
+ CommandParameter="{x:Bind SignInWindow}" />
+ CommandParameter="{x:Bind SignInWindow}" />
diff --git a/App/Views/Pages/SignInTokenPage.xaml.cs b/App/Views/Pages/SignInTokenPage.xaml.cs
index 1219508..f471dcd 100644
--- a/App/Views/Pages/SignInTokenPage.xaml.cs
+++ b/App/Views/Pages/SignInTokenPage.xaml.cs
@@ -1,5 +1,6 @@
using Coder.Desktop.App.ViewModels;
using Microsoft.UI.Xaml.Controls;
+using Windows.System;
namespace Coder.Desktop.App.Views.Pages;
@@ -17,4 +18,13 @@ public SignInTokenPage(SignInWindow parent, SignInViewModel viewModel)
ViewModel = viewModel;
SignInWindow = parent;
}
+
+ private async void PasswordBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
+ {
+ if (e.Key == VirtualKey.Enter)
+ {
+ await ViewModel.TokenPage_SignIn(SignInWindow);
+ e.Handled = true;
+ }
+ }
}
diff --git a/App/Views/Pages/SignInUrlPage.xaml b/App/Views/Pages/SignInUrlPage.xaml
index 76f6a3a..0da263b 100644
--- a/App/Views/Pages/SignInUrlPage.xaml
+++ b/App/Views/Pages/SignInUrlPage.xaml
@@ -6,8 +6,7 @@
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"
- mc:Ignorable="d"
- Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
+ mc:Ignorable="d">
+ Text="{x:Bind ViewModel.CoderUrl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
+ KeyDown="TextBox_KeyDown"/>
diff --git a/App/Views/Pages/SignInUrlPage.xaml.cs b/App/Views/Pages/SignInUrlPage.xaml.cs
index 175a8c2..3ba4fe3 100644
--- a/App/Views/Pages/SignInUrlPage.xaml.cs
+++ b/App/Views/Pages/SignInUrlPage.xaml.cs
@@ -1,5 +1,6 @@
using Coder.Desktop.App.ViewModels;
using Microsoft.UI.Xaml.Controls;
+using Windows.System;
namespace Coder.Desktop.App.Views.Pages;
@@ -17,4 +18,13 @@ public SignInUrlPage(SignInWindow parent, SignInViewModel viewModel)
ViewModel = viewModel;
SignInWindow = parent;
}
+
+ private void TextBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
+ {
+ if (e.Key == VirtualKey.Enter)
+ {
+ ViewModel.UrlPage_Next(SignInWindow);
+ e.Handled = true;
+ }
+ }
}
diff --git a/App/Views/Pages/TrayWindowDisconnectedPage.xaml b/App/Views/Pages/TrayWindowDisconnectedPage.xaml
index 6675f8d..936c65f 100644
--- a/App/Views/Pages/TrayWindowDisconnectedPage.xaml
+++ b/App/Views/Pages/TrayWindowDisconnectedPage.xaml
@@ -30,6 +30,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/Pages/TrayWindowMainPage.xaml b/App/Views/Pages/TrayWindowMainPage.xaml
index 42a9abd..80f557d 100644
--- a/App/Views/Pages/TrayWindowMainPage.xaml
+++ b/App/Views/Pages/TrayWindowMainPage.xaml
@@ -25,7 +25,7 @@
Orientation="Vertical"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
- Padding="20,20,20,30"
+ Padding="20,20,20,20"
Spacing="10">
@@ -43,6 +43,8 @@
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Height="14" Width="14"
+ Margin="0,1,0,0">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Margin="0,-3,0,0" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -240,14 +344,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/Pages/TrayWindowMainPage.xaml.cs b/App/Views/Pages/TrayWindowMainPage.xaml.cs
index 5911092..e1cbab3 100644
--- a/App/Views/Pages/TrayWindowMainPage.xaml.cs
+++ b/App/Views/Pages/TrayWindowMainPage.xaml.cs
@@ -18,9 +18,9 @@ public TrayWindowMainPage(TrayWindowViewModel viewModel)
}
// HACK: using XAML to populate the text Runs results in an additional
- // whitespace Run being inserted between the Hostname and the
- // HostnameSuffix. You might think, "OK let's populate the entire TextBlock
- // content from code then!", but this results in the ItemsRepeater
+ // whitespace Run being inserted between the ViewableHostname and the
+ // ViewableHostnameSuffix. You might think, "OK let's populate the entire
+ // TextBlock content from code then!", but this results in the ItemsRepeater
// corrupting it and firing events off to the wrong AgentModel.
//
// This is the best solution I came up with that worked.
@@ -28,12 +28,12 @@ public void AgentHostnameText_OnLoaded(object sender, RoutedEventArgs e)
{
if (sender is not TextBlock textBlock) return;
- var nonEmptyRuns = new List();
+ var nonWhitespaceRuns = new List();
foreach (var inline in textBlock.Inlines)
- if (inline is Run run && !string.IsNullOrWhiteSpace(run.Text))
- nonEmptyRuns.Add(run);
+ if (inline is Run run && run.Text != " ")
+ nonWhitespaceRuns.Add(run);
textBlock.Inlines.Clear();
- foreach (var run in nonEmptyRuns) textBlock.Inlines.Add(run);
+ foreach (var run in nonWhitespaceRuns) textBlock.Inlines.Add(run);
}
}
diff --git a/App/Views/Pages/UpdaterDownloadProgressMainPage.xaml b/App/Views/Pages/UpdaterDownloadProgressMainPage.xaml
new file mode 100644
index 0000000..ba54bea
--- /dev/null
+++ b/App/Views/Pages/UpdaterDownloadProgressMainPage.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/Pages/UpdaterDownloadProgressMainPage.xaml.cs b/App/Views/Pages/UpdaterDownloadProgressMainPage.xaml.cs
new file mode 100644
index 0000000..3ca6cc2
--- /dev/null
+++ b/App/Views/Pages/UpdaterDownloadProgressMainPage.xaml.cs
@@ -0,0 +1,14 @@
+using Microsoft.UI.Xaml.Controls;
+using Coder.Desktop.App.ViewModels;
+
+namespace Coder.Desktop.App.Views.Pages;
+
+public sealed partial class UpdaterDownloadProgressMainPage : Page
+{
+ public readonly UpdaterDownloadProgressViewModel ViewModel;
+ public UpdaterDownloadProgressMainPage(UpdaterDownloadProgressViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ InitializeComponent();
+ }
+}
diff --git a/App/Views/Pages/UpdaterUpdateAvailableMainPage.xaml b/App/Views/Pages/UpdaterUpdateAvailableMainPage.xaml
new file mode 100644
index 0000000..68faee9
--- /dev/null
+++ b/App/Views/Pages/UpdaterUpdateAvailableMainPage.xaml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/Pages/UpdaterUpdateAvailableMainPage.xaml.cs b/App/Views/Pages/UpdaterUpdateAvailableMainPage.xaml.cs
new file mode 100644
index 0000000..cb2634c
--- /dev/null
+++ b/App/Views/Pages/UpdaterUpdateAvailableMainPage.xaml.cs
@@ -0,0 +1,15 @@
+using Microsoft.UI.Xaml.Controls;
+using Coder.Desktop.App.ViewModels;
+
+namespace Coder.Desktop.App.Views.Pages;
+
+public sealed partial class UpdaterUpdateAvailableMainPage : Page
+{
+ public readonly UpdaterUpdateAvailableViewModel ViewModel;
+
+ public UpdaterUpdateAvailableMainPage(UpdaterUpdateAvailableViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ InitializeComponent();
+ }
+}
diff --git a/App/Views/SettingsWindow.xaml b/App/Views/SettingsWindow.xaml
new file mode 100644
index 0000000..512f0f5
--- /dev/null
+++ b/App/Views/SettingsWindow.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/SettingsWindow.xaml.cs b/App/Views/SettingsWindow.xaml.cs
new file mode 100644
index 0000000..f2a0fdb
--- /dev/null
+++ b/App/Views/SettingsWindow.xaml.cs
@@ -0,0 +1,23 @@
+using Coder.Desktop.App.Utils;
+using Coder.Desktop.App.ViewModels;
+using Coder.Desktop.App.Views.Pages;
+using Microsoft.UI.Xaml.Media;
+using WinUIEx;
+
+namespace Coder.Desktop.App.Views;
+
+public sealed partial class SettingsWindow : WindowEx
+{
+ public readonly SettingsViewModel ViewModel;
+
+ public SettingsWindow(SettingsViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ InitializeComponent();
+ TitleBarIcon.SetTitlebarIcon(this);
+
+ RootFrame.Content = new SettingsMainPage(ViewModel);
+
+ this.CenterOnScreen();
+ }
+}
diff --git a/App/Views/SignInWindow.xaml b/App/Views/SignInWindow.xaml
index d2c1326..6d8340c 100644
--- a/App/Views/SignInWindow.xaml
+++ b/App/Views/SignInWindow.xaml
@@ -11,7 +11,7 @@
Title="Sign in to Coder">
-
+
diff --git a/App/Views/SignInWindow.xaml.cs b/App/Views/SignInWindow.xaml.cs
index 3fe4b5c..da68867 100644
--- a/App/Views/SignInWindow.xaml.cs
+++ b/App/Views/SignInWindow.xaml.cs
@@ -1,6 +1,7 @@
using System;
using Windows.Graphics;
using Coder.Desktop.App.Controls;
+using Coder.Desktop.App.Utils;
using Coder.Desktop.App.ViewModels;
using Coder.Desktop.App.Views.Pages;
using Microsoft.UI.Windowing;
@@ -22,7 +23,7 @@ public sealed partial class SignInWindow : Window
public SignInWindow(SignInViewModel viewModel)
{
InitializeComponent();
- SystemBackdrop = new DesktopAcrylicBackdrop();
+ TitleBarIcon.SetTitlebarIcon(this);
RootFrame.SizeChanged += RootFrame_SizeChanged;
_signInUrlPage = new SignInUrlPage(this, viewModel);
diff --git a/App/Views/TrayWindow.xaml b/App/Views/TrayWindow.xaml
index 0d87874..f5b4b01 100644
--- a/App/Views/TrayWindow.xaml
+++ b/App/Views/TrayWindow.xaml
@@ -15,10 +15,17 @@
-
+
+
+
+
diff --git a/App/Views/TrayWindow.xaml.cs b/App/Views/TrayWindow.xaml.cs
index eac24e8..6131e25 100644
--- a/App/Views/TrayWindow.xaml.cs
+++ b/App/Views/TrayWindow.xaml.cs
@@ -1,11 +1,7 @@
-using System;
-using System.Runtime.InteropServices;
-using Windows.Graphics;
-using Windows.System;
-using Windows.UI.Core;
using Coder.Desktop.App.Controls;
using Coder.Desktop.App.Models;
using Coder.Desktop.App.Services;
+using Coder.Desktop.App.Utils;
using Coder.Desktop.App.Views.Pages;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI;
@@ -13,7 +9,13 @@
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Media.Animation;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Windows.Graphics;
+using Windows.System;
+using Windows.UI.Core;
using WinRT.Interop;
using WindowActivatedEventArgs = Microsoft.UI.Xaml.WindowActivatedEventArgs;
@@ -23,18 +25,25 @@ public sealed partial class TrayWindow : Window
{
private const int WIDTH = 300;
- private NativeApi.POINT? _lastActivatePosition;
+ private readonly AppWindow _aw;
+
+ public double ProxyHeight { get; private set; }
+
+ // This is used to know the "start point of the animation"
+ private int _lastWindowHeight;
+ private Storyboard? _currentSb;
private readonly IRpcController _rpcController;
private readonly ICredentialManager _credentialManager;
private readonly ISyncSessionController _syncSessionController;
+ private readonly IUpdateController _updateController;
private readonly TrayWindowLoadingPage _loadingPage;
private readonly TrayWindowDisconnectedPage _disconnectedPage;
private readonly TrayWindowLoginRequiredPage _loginRequiredPage;
private readonly TrayWindowMainPage _mainPage;
public TrayWindow(IRpcController rpcController, ICredentialManager credentialManager,
- ISyncSessionController syncSessionController,
+ ISyncSessionController syncSessionController, IUpdateController updateController,
TrayWindowLoadingPage loadingPage,
TrayWindowDisconnectedPage disconnectedPage, TrayWindowLoginRequiredPage loginRequiredPage,
TrayWindowMainPage mainPage)
@@ -42,6 +51,7 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
_rpcController = rpcController;
_credentialManager = credentialManager;
_syncSessionController = syncSessionController;
+ _updateController = updateController;
_loadingPage = loadingPage;
_disconnectedPage = disconnectedPage;
_loginRequiredPage = loginRequiredPage;
@@ -49,7 +59,6 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
InitializeComponent();
AppWindow.Hide();
- SystemBackdrop = new DesktopAcrylicBackdrop();
Activated += Window_Activated;
RootFrame.SizeChanged += RootFrame_SizeChanged;
@@ -59,8 +68,9 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
SetPageByState(_rpcController.GetState(), _credentialManager.GetCachedCredentials(),
_syncSessionController.GetState());
- // Setting OpenCommand and ExitCommand directly in the .xaml doesn't seem to work for whatever reason.
+ // Setting these directly in the .xaml doesn't seem to work for whatever reason.
TrayIcon.OpenCommand = Tray_OpenCommand;
+ TrayIcon.CheckForUpdatesCommand = Tray_CheckForUpdatesCommand;
TrayIcon.ExitCommand = Tray_ExitCommand;
// Hide the title bar and buttons. WinUi 3 provides a method to do this with
@@ -80,13 +90,37 @@ public TrayWindow(IRpcController rpcController, ICredentialManager credentialMan
var value = 2;
// Best effort. This does not work on Windows 10.
_ = NativeApi.DwmSetWindowAttribute(windowHandle, 33, ref value, Marshal.SizeOf());
+
+ _aw = AppWindow.GetFromWindowId(
+ Win32Interop.GetWindowIdFromWindow(
+ WindowNative.GetWindowHandle(this)));
+ SizeProxy.SizeChanged += (_, e) =>
+ {
+ if (_currentSb is null) return; // nothing running
+
+ var newHeight = (int)Math.Round(
+ e.NewSize.Height * DisplayScale.WindowScale(this));
+
+ var delta = newHeight - _lastWindowHeight;
+ if (delta == 0) return;
+
+ var pos = _aw.Position;
+ var size = _aw.Size;
+
+ pos.Y -= delta; // grow upward
+ size.Height = newHeight;
+
+ _aw.MoveAndResize(
+ new RectInt32(pos.X, pos.Y, size.Width, size.Height));
+
+ _lastWindowHeight = newHeight;
+ };
}
private void SetPageByState(RpcModel rpcModel, CredentialModel credentialModel,
SyncSessionControllerStateModel syncSessionModel)
{
- if (credentialModel.State == CredentialState.Unknown ||
- syncSessionModel.Lifecycle == SyncSessionControllerLifecycle.Uninitialized)
+ if (credentialModel.State == CredentialState.Unknown)
{
SetRootFrame(_loadingPage);
return;
@@ -138,85 +172,114 @@ public void SetRootFrame(Page page)
private void RootFrame_SizeChanged(object sender, SizedFrameEventArgs e)
{
- ResizeWindow(e.NewSize.Height);
- MoveWindow();
+ AnimateWindowHeight(e.NewSize.Height);
}
- private void ResizeWindow()
+ // We need to animate the height change in code-behind, because XAML
+ // storyboard animation timeline is immutable - it cannot be changed
+ // mid-run to accomodate a new height.
+ private void AnimateWindowHeight(double targetHeight)
{
- ResizeWindow(RootFrame.GetContentSize().Height);
+ // If another animation is already running we need to fast forward it.
+ if (_currentSb is { } oldSb)
+ {
+ oldSb.Completed -= OnStoryboardCompleted;
+ // We need to use SkipToFill, because Stop actually sets Height to 0, which
+ // makes the window go haywire.
+ oldSb.SkipToFill();
+ }
+
+ _lastWindowHeight = AppWindow.Size.Height;
+
+ var anim = new DoubleAnimation
+ {
+ To = targetHeight,
+ Duration = TimeSpan.FromMilliseconds(200),
+ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut },
+ EnableDependentAnimation = true
+ };
+
+ Storyboard.SetTarget(anim, SizeProxy);
+ Storyboard.SetTargetProperty(anim, "Height");
+
+ var sb = new Storyboard { Children = { anim } };
+ sb.Completed += OnStoryboardCompleted;
+ sb.Begin();
+
+ _currentSb = sb;
}
- private void ResizeWindow(double height)
+ private void OnStoryboardCompleted(object? sender, object e)
{
- if (height <= 0) height = 100; // will be resolved next frame typically
-
- var scale = DisplayScale.WindowScale(this);
- var newWidth = (int)(WIDTH * scale);
- var newHeight = (int)(height * scale);
- AppWindow.Resize(new SizeInt32(newWidth, newHeight));
+ // We need to remove the event handler after the storyboard completes,
+ // to avoid memory leaks and multiple calls.
+ if (sender is Storyboard sb)
+ sb.Completed -= OnStoryboardCompleted;
+
+ // SizeChanged handler will stop forwarding resize ticks
+ // until we start the next storyboard.
+ _currentSb = null;
}
private void MoveResizeAndActivate()
{
- SaveCursorPos();
- ResizeWindow();
- MoveWindow();
+ var size = CalculateWindowSize(RootFrame.GetContentSize().Height);
+ var pos = CalculateWindowPosition(size);
+ var rect = new RectInt32(pos.X, pos.Y, size.Width, size.Height);
+ AppWindow.MoveAndResize(rect);
AppWindow.Show();
- NativeApi.SetForegroundWindow(WindowNative.GetWindowHandle(this));
+ ForegroundWindow.MakeForeground(this);
}
- private void SaveCursorPos()
+ private SizeInt32 CalculateWindowSize(double height)
{
- var res = NativeApi.GetCursorPos(out var cursorPosition);
- if (res)
- _lastActivatePosition = cursorPosition;
- else
- // When the cursor position is null, we will spawn the window in
- // the bottom right corner of the primary display.
- // TODO: log(?) an error when this happens
- _lastActivatePosition = null;
- }
+ if (height <= 0) height = 100; // will be resolved next frame typically
- private void MoveWindow()
- {
- AppWindow.Move(GetWindowPosition());
+ var scale = DisplayScale.WindowScale(this);
+ var newWidth = (int)(WIDTH * scale);
+ var newHeight = (int)(height * scale);
+
+ return new SizeInt32(newWidth, newHeight);
}
- private PointInt32 GetWindowPosition()
+ private PointInt32 CalculateWindowPosition(SizeInt32 panelSize)
{
- var height = AppWindow.Size.Height;
- var width = AppWindow.Size.Width;
- var cursorPosition = _lastActivatePosition;
- if (cursorPosition is null)
+ var area = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Primary);
+ // whole monitor
+ var bounds = area.OuterBounds;
+ // monitor minus taskbar
+ var workArea = area.WorkArea;
+
+ // get taskbar details - position, gap (size), auto-hide
+ var tb = GetTaskbarInfo(area);
+
+ // safe edges where tray window can touch the screen
+ var safeRight = workArea.X + workArea.Width;
+ var safeBottom = workArea.Y + workArea.Height;
+
+ // if the taskbar is auto-hidden at the bottom, stay clear of its reveal band
+ if (tb.Position == TaskbarPosition.Bottom && tb.AutoHide)
+ safeBottom -= tb.Gap; // shift everything up by its thickness
+
+ // pick corner & position the panel
+ int x, y;
+ switch (tb.Position)
{
- var primaryWorkArea = DisplayArea.Primary.WorkArea;
- return new PointInt32(
- primaryWorkArea.Width - width,
- primaryWorkArea.Height - height
- );
- }
-
- // Spawn the window to the top right of the cursor.
- var x = cursorPosition.Value.X + 10;
- var y = cursorPosition.Value.Y - 10 - height;
-
- var workArea = DisplayArea.GetFromPoint(
- new PointInt32(cursorPosition.Value.X, cursorPosition.Value.Y),
- DisplayAreaFallback.Primary
- ).WorkArea;
-
- // Adjust if the window goes off the right edge of the display.
- if (x + width > workArea.X + workArea.Width) x = workArea.X + workArea.Width - width;
-
- // Adjust if the window goes off the bottom edge of the display.
- if (y + height > workArea.Y + workArea.Height) y = workArea.Y + workArea.Height - height;
+ case TaskbarPosition.Left: // for Left we will stick to the left-bottom corner
+ x = bounds.X + tb.Gap; // just right of the bar
+ y = safeBottom - panelSize.Height;
+ break;
- // Adjust if the window goes off the left edge of the display (somehow).
- if (x < workArea.X) x = workArea.X;
+ case TaskbarPosition.Top: // for Top we will stick to the top-right corner
+ x = safeRight - panelSize.Width;
+ y = bounds.Y + tb.Gap; // just below the bar
+ break;
- // Adjust if the window goes off the top edge of the display (somehow).
- if (y < workArea.Y) y = workArea.Y;
+ default: // Bottom or Right bar we will stick to the bottom-right corner
+ x = safeRight - panelSize.Width;
+ y = safeBottom - panelSize.Height;
+ break;
+ }
return new PointInt32(x, y);
}
@@ -239,6 +302,13 @@ private void Tray_Open()
MoveResizeAndActivate();
}
+ [RelayCommand]
+ private async Task Tray_CheckForUpdates()
+ {
+ // Handles errors itself for the most part.
+ await _updateController.CheckForUpdatesNow();
+ }
+
[RelayCommand]
private void Tray_Exit()
{
@@ -254,13 +324,77 @@ public static class NativeApi
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out POINT lpPoint);
- [DllImport("user32.dll")]
- public static extern bool SetForegroundWindow(IntPtr hwnd);
-
public struct POINT
{
public int X;
public int Y;
}
}
+
+ internal enum TaskbarPosition { Left, Top, Right, Bottom }
+
+ internal readonly record struct TaskbarInfo(TaskbarPosition Position, int Gap, bool AutoHide);
+
+ // -----------------------------------------------------------------------------
+ // Taskbar helpers – ABM_GETTASKBARPOS / ABM_GETSTATE via SHAppBarMessage
+ // -----------------------------------------------------------------------------
+ private static TaskbarInfo GetTaskbarInfo(DisplayArea area)
+ {
+ var data = new APPBARDATA
+ {
+ cbSize = (uint)Marshal.SizeOf()
+ };
+
+ // Locate the taskbar.
+ if (SHAppBarMessage(ABM_GETTASKBARPOS, ref data) == 0)
+ return new TaskbarInfo(TaskbarPosition.Bottom, 0, false); // failsafe
+
+ var autoHide = (SHAppBarMessage(ABM_GETSTATE, ref data) & ABS_AUTOHIDE) != 0;
+
+ // Use uEdge instead of guessing from the RECT.
+ var pos = data.uEdge switch
+ {
+ ABE_LEFT => TaskbarPosition.Left,
+ ABE_TOP => TaskbarPosition.Top,
+ ABE_RIGHT => TaskbarPosition.Right,
+ _ => TaskbarPosition.Bottom, // ABE_BOTTOM or anything unexpected
+ };
+
+ // Thickness (gap) = shorter side of the rect.
+ var gap = (pos == TaskbarPosition.Left || pos == TaskbarPosition.Right)
+ ? data.rc.right - data.rc.left // width
+ : data.rc.bottom - data.rc.top; // height
+
+ return new TaskbarInfo(pos, gap, autoHide);
+ }
+
+ // ------------- P/Invoke plumbing -------------
+ private const uint ABM_GETTASKBARPOS = 0x0005;
+ private const uint ABM_GETSTATE = 0x0004;
+ private const int ABS_AUTOHIDE = 0x0001;
+
+ private const int ABE_LEFT = 0; // values returned in APPBARDATA.uEdge
+ private const int ABE_TOP = 1;
+ private const int ABE_RIGHT = 2;
+ private const int ABE_BOTTOM = 3;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct APPBARDATA
+ {
+ public uint cbSize;
+ public IntPtr hWnd;
+ public uint uCallbackMessage;
+ public uint uEdge; // contains ABE_* value
+ public RECT rc;
+ public int lParam;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct RECT
+ {
+ public int left, top, right, bottom;
+ }
+
+ [DllImport("shell32.dll", CharSet = CharSet.Auto)]
+ private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
}
diff --git a/App/Views/UpdaterCheckingForUpdatesWindow.xaml b/App/Views/UpdaterCheckingForUpdatesWindow.xaml
new file mode 100644
index 0000000..f8a02b4
--- /dev/null
+++ b/App/Views/UpdaterCheckingForUpdatesWindow.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/UpdaterCheckingForUpdatesWindow.xaml.cs b/App/Views/UpdaterCheckingForUpdatesWindow.xaml.cs
new file mode 100644
index 0000000..10a3ae2
--- /dev/null
+++ b/App/Views/UpdaterCheckingForUpdatesWindow.xaml.cs
@@ -0,0 +1,32 @@
+using System;
+using Coder.Desktop.App.Utils;
+using NetSparkleUpdater.Interfaces;
+using WinUIEx;
+
+namespace Coder.Desktop.App.Views;
+
+public sealed partial class UpdaterCheckingForUpdatesWindow : WindowEx, ICheckingForUpdates
+{
+ // Implements ICheckingForUpdates
+ public event EventHandler? UpdatesUIClosing;
+
+ public UpdaterCheckingForUpdatesWindow()
+ {
+ InitializeComponent();
+ TitleBarIcon.SetTitlebarIcon(this);
+ AppWindow.Hide();
+
+ Closed += (_, _) => UpdatesUIClosing?.Invoke(this, EventArgs.Empty);
+ }
+
+ void ICheckingForUpdates.Show()
+ {
+ AppWindow.Show();
+ this.CenterOnScreen();
+ }
+
+ void ICheckingForUpdates.Close()
+ {
+ Close();
+ }
+}
diff --git a/App/Views/UpdaterDownloadProgressWindow.xaml b/App/Views/UpdaterDownloadProgressWindow.xaml
new file mode 100644
index 0000000..8976766
--- /dev/null
+++ b/App/Views/UpdaterDownloadProgressWindow.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/UpdaterDownloadProgressWindow.xaml.cs b/App/Views/UpdaterDownloadProgressWindow.xaml.cs
new file mode 100644
index 0000000..a00123c
--- /dev/null
+++ b/App/Views/UpdaterDownloadProgressWindow.xaml.cs
@@ -0,0 +1,83 @@
+using Coder.Desktop.App.Utils;
+using Coder.Desktop.App.ViewModels;
+using Coder.Desktop.App.Views.Pages;
+using NetSparkleUpdater.Events;
+using NetSparkleUpdater.Interfaces;
+using WinUIEx;
+using WindowEventArgs = Microsoft.UI.Xaml.WindowEventArgs;
+
+namespace Coder.Desktop.App.Views;
+
+public sealed partial class UpdaterDownloadProgressWindow : WindowEx, IDownloadProgress
+{
+ // Implements IDownloadProgress
+ public event DownloadInstallEventHandler? DownloadProcessCompleted;
+
+ public UpdaterDownloadProgressViewModel ViewModel;
+
+ private bool _downloadProcessCompletedInvoked;
+
+ public UpdaterDownloadProgressWindow(UpdaterDownloadProgressViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ ViewModel.DownloadProcessCompleted += (_, args) => SendResponse(args);
+
+ InitializeComponent();
+ TitleBarIcon.SetTitlebarIcon(this);
+ AppWindow.Hide();
+
+ RootFrame.Content = new UpdaterDownloadProgressMainPage(ViewModel);
+
+ Closed += UpdaterDownloadProgressWindow_Closed;
+ }
+
+ public void SendResponse(DownloadInstallEventArgs args)
+ {
+ if (_downloadProcessCompletedInvoked)
+ return;
+ _downloadProcessCompletedInvoked = true;
+ DownloadProcessCompleted?.Invoke(this, args);
+ }
+
+ private void UpdaterDownloadProgressWindow_Closed(object sender, WindowEventArgs args)
+ {
+ SendResponse(new DownloadInstallEventArgs(false)); // Cancel
+ }
+
+ void IDownloadProgress.SetDownloadAndInstallButtonEnabled(bool shouldBeEnabled)
+ {
+ ViewModel.SetActionButtonEnabled(shouldBeEnabled);
+ }
+
+ void IDownloadProgress.Show()
+ {
+ AppWindow.Show();
+ this.CenterOnScreen();
+ }
+
+ void IDownloadProgress.Close()
+ {
+ Close();
+ }
+
+ void IDownloadProgress.OnDownloadProgressChanged(object sender, ItemDownloadProgressEventArgs args)
+ {
+ ViewModel.SetDownloadProgress((ulong)args.BytesReceived, (ulong)args.TotalBytesToReceive);
+ }
+
+ void IDownloadProgress.FinishedDownloadingFile(bool isDownloadedFileValid)
+ {
+ ViewModel.SetFinishedDownloading(isDownloadedFileValid);
+ }
+
+ bool IDownloadProgress.DisplayErrorMessage(string errorMessage)
+ {
+ // TODO: this is pretty lazy but works for now
+ _ = new MessageWindow(
+ "Download failed",
+ errorMessage,
+ "Coder Desktop Updater");
+ Close();
+ return true;
+ }
+}
diff --git a/App/Views/UpdaterUpdateAvailableWindow.xaml b/App/Views/UpdaterUpdateAvailableWindow.xaml
new file mode 100644
index 0000000..9e13972
--- /dev/null
+++ b/App/Views/UpdaterUpdateAvailableWindow.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/App/Views/UpdaterUpdateAvailableWindow.xaml.cs b/App/Views/UpdaterUpdateAvailableWindow.xaml.cs
new file mode 100644
index 0000000..0a8e32b
--- /dev/null
+++ b/App/Views/UpdaterUpdateAvailableWindow.xaml.cs
@@ -0,0 +1,89 @@
+using Coder.Desktop.App.Utils;
+using Coder.Desktop.App.ViewModels;
+using Coder.Desktop.App.Views.Pages;
+using Microsoft.UI.Xaml;
+using NetSparkleUpdater;
+using NetSparkleUpdater.Enums;
+using NetSparkleUpdater.Events;
+using NetSparkleUpdater.Interfaces;
+using WinUIEx;
+
+namespace Coder.Desktop.App.Views;
+
+public sealed partial class UpdaterUpdateAvailableWindow : WindowEx, IUpdateAvailable
+{
+ public readonly UpdaterUpdateAvailableViewModel ViewModel;
+
+ // Implements IUpdateAvailable
+ public UpdateAvailableResult Result => ViewModel.Result;
+ // Implements IUpdateAvailable
+ public AppCastItem CurrentItem => ViewModel.CurrentItem;
+ // Implements IUpdateAvailable
+ public event UserRespondedToUpdate? UserResponded;
+
+ private bool _respondedToUpdate;
+
+ public UpdaterUpdateAvailableWindow(UpdaterUpdateAvailableViewModel viewModel)
+ {
+ ViewModel = viewModel;
+ ViewModel.UserResponded += (_, args) =>
+ UserRespondedToUpdateCheck(args.Result);
+
+ InitializeComponent();
+ TitleBarIcon.SetTitlebarIcon(this);
+ AppWindow.Hide();
+
+ RootFrame.Content = new UpdaterUpdateAvailableMainPage(ViewModel);
+
+ Closed += UpdaterUpdateAvailableWindow_Closed;
+ }
+
+ private void UpdaterUpdateAvailableWindow_Closed(object sender, WindowEventArgs args)
+ {
+ UserRespondedToUpdateCheck(UpdateAvailableResult.None);
+ }
+
+ void IUpdateAvailable.Show()
+ {
+ AppWindow.Show();
+ this.CenterOnScreen();
+ }
+
+ void IUpdateAvailable.Close()
+ {
+ // The NetSparkle built-in Avalonia UI does this "just in case"
+ UserRespondedToUpdateCheck(UpdateAvailableResult.None);
+ Close();
+ }
+
+ void IUpdateAvailable.HideReleaseNotes()
+ {
+ ViewModel.HideReleaseNotes();
+ }
+
+ void IUpdateAvailable.HideRemindMeLaterButton()
+ {
+ ViewModel.HideRemindMeLaterButton();
+ }
+
+ void IUpdateAvailable.HideSkipButton()
+ {
+ ViewModel.HideSkipButton();
+ }
+
+ void IUpdateAvailable.BringToFront()
+ {
+ Activate();
+ ForegroundWindow.MakeForeground(this);
+ }
+
+ private void UserRespondedToUpdateCheck(UpdateAvailableResult response)
+ {
+ if (_respondedToUpdate)
+ return;
+ _respondedToUpdate = true;
+ UserResponded?.Invoke(this, new UpdateResponseEventArgs(response, CurrentItem));
+ // Prevent further interaction.
+ Close();
+ }
+}
diff --git a/App/packages.lock.json b/App/packages.lock.json
index 405ea61..452aae8 100644
--- a/App/packages.lock.json
+++ b/App/packages.lock.json
@@ -8,6 +8,36 @@
"resolved": "8.4.0",
"contentHash": "tqVU8yc/ADO9oiTRyTnwhFN68hCwvkliMierptWOudIAvWY1mWCh5VFh+guwHJmpMwfg0J0rY+yyd5Oy7ty9Uw=="
},
+ "CommunityToolkit.WinUI.Controls.Primitives": {
+ "type": "Direct",
+ "requested": "[8.2.250402, )",
+ "resolved": "8.2.250402",
+ "contentHash": "Wx3t1zADrzBWDar45uRl+lmSxDO5Vx7tTMFm/mNgl3fs5xSQ1ySPdGqD10EFov3rkKc5fbpHGW5xj8t62Yisvg==",
+ "dependencies": {
+ "CommunityToolkit.WinUI.Extensions": "8.2.250402",
+ "Microsoft.WindowsAppSDK": "1.6.250108002"
+ }
+ },
+ "CommunityToolkit.WinUI.Controls.SettingsControls": {
+ "type": "Direct",
+ "requested": "[8.2.250402, )",
+ "resolved": "8.2.250402",
+ "contentHash": "whJNIyxVwwLmmCS63m91r0aL13EYgdKDE0ERh2e0G2U5TUeYQQe2XRODGGr/ceBRvqu6SIvq1sXxVwglSMiJMg==",
+ "dependencies": {
+ "CommunityToolkit.WinUI.Triggers": "8.2.250402",
+ "Microsoft.WindowsAppSDK": "1.6.250108002"
+ }
+ },
+ "CommunityToolkit.WinUI.Extensions": {
+ "type": "Direct",
+ "requested": "[8.2.250402, )",
+ "resolved": "8.2.250402",
+ "contentHash": "rAOYzNX6kdUeeE1ejGd6Q8B+xmyZvOrWFUbqCgOtP8OQsOL66en9ZQTtzxAlaaFC4qleLvnKcn8FJFBezujOlw==",
+ "dependencies": {
+ "CommunityToolkit.Common": "8.2.1",
+ "Microsoft.WindowsAppSDK": "1.6.250108002"
+ }
+ },
"DependencyPropertyGenerator": {
"type": "Direct",
"requested": "[1.5.0, )",
@@ -28,51 +58,51 @@
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Direct",
- "requested": "[9.0.1, )",
- "resolved": "9.0.1",
- "contentHash": "qZI42ASAe3hr2zMSA6UjM92pO1LeDq5DcwkgSowXXPY8I56M76pEKrnmsKKbxagAf39AJxkH2DY4sb72ixyOrg==",
+ "requested": "[9.0.4, )",
+ "resolved": "9.0.4",
+ "contentHash": "f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4"
}
},
"Microsoft.Extensions.Hosting": {
"type": "Direct",
- "requested": "[9.0.1, )",
- "resolved": "9.0.1",
- "contentHash": "3wZNcVvC8RW44HDqqmIq+BqF5pgmTQdbNdR9NyYw33JSMnJuclwoJ2PEkrJ/KvD1U/hmqHVL3l5If+Hn3D1fWA==",
- "dependencies": {
- "Microsoft.Extensions.Configuration": "9.0.1",
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.Configuration.Binder": "9.0.1",
- "Microsoft.Extensions.Configuration.CommandLine": "9.0.1",
- "Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.1",
- "Microsoft.Extensions.Configuration.FileExtensions": "9.0.1",
- "Microsoft.Extensions.Configuration.Json": "9.0.1",
- "Microsoft.Extensions.Configuration.UserSecrets": "9.0.1",
- "Microsoft.Extensions.DependencyInjection": "9.0.1",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Diagnostics": "9.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1",
- "Microsoft.Extensions.FileProviders.Physical": "9.0.1",
- "Microsoft.Extensions.Hosting.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging.Configuration": "9.0.1",
- "Microsoft.Extensions.Logging.Console": "9.0.1",
- "Microsoft.Extensions.Logging.Debug": "9.0.1",
- "Microsoft.Extensions.Logging.EventLog": "9.0.1",
- "Microsoft.Extensions.Logging.EventSource": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1"
+ "requested": "[9.0.4, )",
+ "resolved": "9.0.4",
+ "contentHash": "1rZwLE+tTUIyZRUzmlk/DQj+v+Eqox+rjb+X7Fi+cYTbQfIZPYwpf1pVybsV3oje8+Pe4GaNukpBVUlPYeQdeQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "9.0.4",
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Configuration.Binder": "9.0.4",
+ "Microsoft.Extensions.Configuration.CommandLine": "9.0.4",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.4",
+ "Microsoft.Extensions.Configuration.FileExtensions": "9.0.4",
+ "Microsoft.Extensions.Configuration.Json": "9.0.4",
+ "Microsoft.Extensions.Configuration.UserSecrets": "9.0.4",
+ "Microsoft.Extensions.DependencyInjection": "9.0.4",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Diagnostics": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Physical": "9.0.4",
+ "Microsoft.Extensions.Hosting.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging.Configuration": "9.0.4",
+ "Microsoft.Extensions.Logging.Console": "9.0.4",
+ "Microsoft.Extensions.Logging.Debug": "9.0.4",
+ "Microsoft.Extensions.Logging.EventLog": "9.0.4",
+ "Microsoft.Extensions.Logging.EventSource": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Options": {
"type": "Direct",
- "requested": "[9.0.1, )",
- "resolved": "9.0.1",
- "contentHash": "nggoNKnWcsBIAaOWHA+53XZWrslC7aGeok+aR+epDPRy7HI7GwMnGZE8yEsL2Onw7kMOHVHwKcsDls1INkNUJQ==",
+ "requested": "[9.0.4, )",
+ "resolved": "9.0.4",
+ "contentHash": "fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Primitives": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.WindowsAppSDK": {
@@ -85,6 +115,57 @@
"Microsoft.Windows.SDK.BuildTools": "10.0.22621.756"
}
},
+ "NetSparkleUpdater.SparkleUpdater": {
+ "type": "Direct",
+ "requested": "[3.0.2, )",
+ "resolved": "3.0.2",
+ "contentHash": "ruDV/hBjZX7DTFMvcJAgA8bUEB8zkq23i/zwpKKWr/vK/IxWIQESYRfP2JpQfKSqlVFNL5uOlJ86wV6nJAi09w==",
+ "dependencies": {
+ "NetSparkleUpdater.Chaos.NaCl": "0.9.3"
+ }
+ },
+ "Serilog.Extensions.Hosting": {
+ "type": "Direct",
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
+ "Microsoft.Extensions.Hosting.Abstractions": "9.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.0",
+ "Serilog": "4.2.0",
+ "Serilog.Extensions.Logging": "9.0.0"
+ }
+ },
+ "Serilog.Settings.Configuration": {
+ "type": "Direct",
+ "requested": "[9.0.0, )",
+ "resolved": "9.0.0",
+ "contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Binder": "9.0.0",
+ "Microsoft.Extensions.DependencyModel": "9.0.0",
+ "Serilog": "4.2.0"
+ }
+ },
+ "Serilog.Sinks.Debug": {
+ "type": "Direct",
+ "requested": "[3.0.0, )",
+ "resolved": "3.0.0",
+ "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==",
+ "dependencies": {
+ "Serilog": "4.0.0"
+ }
+ },
+ "Serilog.Sinks.File": {
+ "type": "Direct",
+ "requested": "[6.0.0, )",
+ "resolved": "6.0.0",
+ "contentHash": "lxjg89Y8gJMmFxVkbZ+qDgjl+T4yC5F7WSLTvA+5q0R04tfKVLRL/EHpYoJ/MEQd2EeCKDuylBIVnAYMotmh2A==",
+ "dependencies": {
+ "Serilog": "4.0.0"
+ }
+ },
"WinUIEx": {
"type": "Direct",
"requested": "[2.5.1, )",
@@ -94,6 +175,29 @@
"Microsoft.WindowsAppSDK": "1.6.240829007"
}
},
+ "CommunityToolkit.Common": {
+ "type": "Transitive",
+ "resolved": "8.2.1",
+ "contentHash": "LWuhy8cQKJ/MYcy3XafJ916U3gPH/YDvYoNGWyQWN11aiEKCZszzPOTJAOvBjP9yG8vHmIcCyPUt4L82OK47Iw=="
+ },
+ "CommunityToolkit.WinUI.Helpers": {
+ "type": "Transitive",
+ "resolved": "8.2.250402",
+ "contentHash": "DThBXB4hT3/aJ7xFKQJw/C0ZEs1QhZL7QG6AFOYcpnGWNlv3tkF761PFtTyhpNQrR1AFfzml5zG+zWfFbKs6Mw==",
+ "dependencies": {
+ "CommunityToolkit.WinUI.Extensions": "8.2.250402",
+ "Microsoft.WindowsAppSDK": "1.6.250108002"
+ }
+ },
+ "CommunityToolkit.WinUI.Triggers": {
+ "type": "Transitive",
+ "resolved": "8.2.250402",
+ "contentHash": "laHIrBQkwQCurTNSQdGdEXUyoCpqY8QFXSybDM/Q1Ti/23xL+sRX/gHe3pP8uMM59bcwYbYlMRCbIaLnxnrdjw==",
+ "dependencies": {
+ "CommunityToolkit.WinUI.Helpers": "8.2.250402",
+ "Microsoft.WindowsAppSDK": "1.6.250108002"
+ }
+ },
"Google.Protobuf": {
"type": "Transitive",
"resolved": "3.29.3",
@@ -139,240 +243,249 @@
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "VuthqFS+ju6vT8W4wevdhEFiRi1trvQtkzWLonApfF5USVzzDcTBoY3F24WvN/tffLSrycArVfX1bThm/9xY2A==",
+ "resolved": "9.0.4",
+ "contentHash": "KIVBrMbItnCJDd1RF4KEaE8jZwDJcDUJW5zXpbwQ05HNYTK1GveHxHK0B3SjgDJuR48GRACXAO+BLhL8h34S7g==",
"dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.Primitives": "9.0.1"
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "+4hfFIY1UjBCXFTTOd+ojlDPq6mep3h5Vq5SYE3Pjucr7dNXmq4S/6P/LoVnZFz2e/5gWp/om4svUFgznfULcA==",
+ "resolved": "9.0.4",
+ "contentHash": "0LN/DiIKvBrkqp7gkF3qhGIeZk6/B63PthAHjQsxymJfIBcz0kbf4/p/t4lMgggVxZ+flRi5xvTwlpPOoZk8fg==",
"dependencies": {
- "Microsoft.Extensions.Primitives": "9.0.1"
+ "Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "w7kAyu1Mm7eParRV6WvGNNwA8flPTub16fwH49h7b/yqJZFTgYxnOVCuiah3G2bgseJMEq4DLjjsyQRvsdzRgA==",
+ "resolved": "9.0.4",
+ "contentHash": "cdrjcl9RIcwt3ECbnpP0Gt1+pkjdW90mq5yFYy8D9qRj2NqFFcv3yDp141iEamsd9E218sGxK8WHaIOcrqgDJg==",
"dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1"
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.CommandLine": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "5WC1OsXfljC1KHEyL0yefpAyt1UZjrZ0/xyOqFowc5VntbE79JpCYOTSYFlxEuXm3Oq5xsgU2YXeZLTgAAX+DA==",
+ "resolved": "9.0.4",
+ "contentHash": "TbM2HElARG7z1gxwakdppmOkm1SykPqDcu3EF97daEwSb/+TXnRrFfJtF+5FWWxcsNhbRrmLfS2WszYcab7u1A==",
"dependencies": {
- "Microsoft.Extensions.Configuration": "9.0.1",
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1"
+ "Microsoft.Extensions.Configuration": "9.0.4",
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "5HShUdF8KFAUSzoEu0DOFbX09FlcFtHxEalowyjM7Kji0EjdF0DLjHajb2IBvoqsExAYox+Z2GfbfGF7dH7lKQ==",
+ "resolved": "9.0.4",
+ "contentHash": "2IGiG3FtVnD83IA6HYGuNei8dOw455C09yEhGl8bjcY6aGZgoC6yhYvDnozw8wlTowfoG9bxVrdTsr2ACZOYHg==",
"dependencies": {
- "Microsoft.Extensions.Configuration": "9.0.1",
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1"
+ "Microsoft.Extensions.Configuration": "9.0.4",
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.FileExtensions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "QBOI8YVAyKqeshYOyxSe6co22oag431vxMu5xQe1EjXMkYE4xK4J71xLCW3/bWKmr9Aoy1VqGUARSLFnotk4Bg==",
+ "resolved": "9.0.4",
+ "contentHash": "UY864WQ3AS2Fkc8fYLombWnjrXwYt+BEHHps0hY4sxlgqaVW06AxbpgRZjfYf8PyRbplJqruzZDB/nSLT+7RLQ==",
"dependencies": {
- "Microsoft.Extensions.Configuration": "9.0.1",
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1",
- "Microsoft.Extensions.FileProviders.Physical": "9.0.1",
- "Microsoft.Extensions.Primitives": "9.0.1"
+ "Microsoft.Extensions.Configuration": "9.0.4",
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Physical": "9.0.4",
+ "Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.Json": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "z+g+lgPET1JRDjsOkFe51rkkNcnJgvOK5UIpeTfF1iAi0GkBJz5/yUuTa8a9V8HUh4gj4xFT5WGoMoXoSDKfGg==",
+ "resolved": "9.0.4",
+ "contentHash": "vVXI70CgT/dmXV3MM+n/BR2rLXEoAyoK0hQT+8MrbCMuJBiLRxnTtSrksNiASWCwOtxo/Tyy7CO8AGthbsYxnw==",
"dependencies": {
- "Microsoft.Extensions.Configuration": "9.0.1",
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.Configuration.FileExtensions": "9.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1",
- "System.Text.Json": "9.0.1"
+ "Microsoft.Extensions.Configuration": "9.0.4",
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Configuration.FileExtensions": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4",
+ "System.Text.Json": "9.0.4"
}
},
"Microsoft.Extensions.Configuration.UserSecrets": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "esGPOgLZ1tZddEomexhrU+LJ5YIsuJdkh0tU7r4WVpNZ15dLuMPqPW4Xe4txf3T2PDUX2ILe3nYQEDjZjfSEJg==",
+ "resolved": "9.0.4",
+ "contentHash": "zuvyC72gJkJyodyGowCuz3EQ1QvzNXJtKusuRzmjoHr17aeB3X0aSiKFB++HMHnQIWWlPOBf9YHTQfEqzbgl1g==",
"dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.Configuration.Json": "9.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1",
- "Microsoft.Extensions.FileProviders.Physical": "9.0.1"
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Configuration.Json": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Physical": "9.0.4"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "Tr74eP0oQ3AyC24ch17N8PuEkrPbD0JqIfENCYqmgKYNOmL8wQKzLJu3ObxTUDrjnn4rHoR1qKa37/eQyHmCDA=="
+ "resolved": "9.0.4",
+ "contentHash": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg=="
+ },
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "9.0.0",
+ "contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA==",
+ "dependencies": {
+ "System.Text.Encodings.Web": "9.0.0",
+ "System.Text.Json": "9.0.0"
+ }
},
"Microsoft.Extensions.Diagnostics": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "4ZmP6turxMFsNwK/MCko2fuIITaYYN/eXyyIRq1FjLDKnptdbn6xMb7u0zfSMzCGpzkx4RxH/g1jKN2IchG7uA==",
+ "resolved": "9.0.4",
+ "contentHash": "1bCSQrGv9+bpF5MGKF6THbnRFUZqQDrWPA39NDeVW9djeHBmow8kX4SX6/8KkeKI8gmUDG7jsG/bVuNAcY/ATQ==",
"dependencies": {
- "Microsoft.Extensions.Configuration": "9.0.1",
- "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.1",
- "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.1"
+ "Microsoft.Extensions.Configuration": "9.0.4",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.4"
}
},
"Microsoft.Extensions.Diagnostics.Abstractions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "pfAPuVtHvG6dvZtAa0OQbXdDqq6epnr8z0/IIUjdmV0tMeI8Aj9KxDXvdDvqr+qNHTkmA7pZpChNxwNZt4GXVg==",
+ "resolved": "9.0.4",
+ "contentHash": "IAucBcHYtiCmMyFag+Vrp5m+cjGRlDttJk9Vx7Dqpq+Ama4BzVUOk0JARQakgFFr7ZTBSgLKlHmtY5MiItB7Cg==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1",
- "System.Diagnostics.DiagnosticSource": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4",
+ "System.Diagnostics.DiagnosticSource": "9.0.4"
}
},
"Microsoft.Extensions.FileProviders.Abstractions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "DguZYt1DWL05+8QKWL3b6bW7A2pC5kYFMY5iXM6W2M23jhvcNa8v6AU8PvVJBcysxHwr9/jax0agnwoBumsSwg==",
+ "resolved": "9.0.4",
+ "contentHash": "gQN2o/KnBfVk6Bd71E2YsvO5lsqrqHmaepDGk+FB/C4aiQY9B0XKKNKfl5/TqcNOs9OEithm4opiMHAErMFyEw==",
"dependencies": {
- "Microsoft.Extensions.Primitives": "9.0.1"
+ "Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.FileProviders.Physical": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "TKDMNRS66UTMEVT38/tU9hA63UTMvzI3DyNm5mx8+JCf3BaOtxgrvWLCI1y3J52PzT5yNl/T2KN5Z0KbApLZcg==",
+ "resolved": "9.0.4",
+ "contentHash": "qkQ9V7KFZdTWNThT7ke7E/Jad38s46atSs3QUYZB8f3thBTrcrousdY4Y/tyCtcH5YjsPSiByjuN+L8W/ThMQg==",
"dependencies": {
- "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1",
- "Microsoft.Extensions.FileSystemGlobbing": "9.0.1",
- "Microsoft.Extensions.Primitives": "9.0.1"
+ "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4",
+ "Microsoft.Extensions.FileSystemGlobbing": "9.0.4",
+ "Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.FileSystemGlobbing": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "Mxcp9NXuQMvAnudRZcgIb5SqlWrlullQzntBLTwuv0MPIJ5LqiGwbRqiyxgdk+vtCoUkplb0oXy5kAw1t469Ug=="
+ "resolved": "9.0.4",
+ "contentHash": "05Lh2ItSk4mzTdDWATW9nEcSybwprN8Tz42Fs5B+jwdXUpauktdAQUI1Am4sUQi2C63E5hvQp8gXvfwfg9mQGQ=="
},
"Microsoft.Extensions.Hosting.Abstractions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "CwSMhLNe8HLkfbFzdz0CHWJhtWH3TtfZSicLBd/itFD+NqQtfGHmvqXHQbaFFl3mQB5PBb2gxwzWQyW2pIj7PA==",
+ "resolved": "9.0.4",
+ "contentHash": "bXkwRPMo4x19YKH6/V9XotU7KYQJlihXhcWO1RDclAY3yfY3XNg4QtSEBvng4kK/DnboE0O/nwSl+6Jiv9P+FA==",
"dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.1",
- "Microsoft.Extensions.FileProviders.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1"
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.4",
+ "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4"
}
},
"Microsoft.Extensions.Logging": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "E/k5r7S44DOW+08xQPnNbO8DKAQHhkspDboTThNJ6Z3/QBb4LC6gStNWzVmy3IvW7sUD+iJKf4fj0xEkqE7vnQ==",
+ "resolved": "9.0.4",
+ "contentHash": "xW6QPYsqhbuWBO9/1oA43g/XPKbohJx+7G8FLQgQXIriYvY7s+gxr2wjQJfRoPO900dvvv2vVH7wZovG+M1m6w==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "w2gUqXN/jNIuvqYwX3lbXagsizVNXYyt6LlF57+tMve4JYCEgCMMAjRce6uKcDASJgpMbErRT1PfHy2OhbkqEA==",
+ "resolved": "9.0.4",
+ "contentHash": "0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "System.Diagnostics.DiagnosticSource": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "System.Diagnostics.DiagnosticSource": "9.0.4"
}
},
"Microsoft.Extensions.Logging.Configuration": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "MeZePlyu3/74Wk4FHYSzXijADJUhWa7gxtaphLxhS8zEPWdJuBCrPo0sezdCSZaKCL+cZLSLobrb7xt2zHOxZQ==",
+ "resolved": "9.0.4",
+ "contentHash": "/kF+rSnoo3/nIwGzWsR4RgBnoTOdZ3lzz2qFRyp/GgaNid4j6hOAQrs/O+QHXhlcAdZxjg37MvtIE+pAvIgi9g==",
"dependencies": {
- "Microsoft.Extensions.Configuration": "9.0.1",
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.Configuration.Binder": "9.0.1",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1",
- "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.1"
+ "Microsoft.Extensions.Configuration": "9.0.4",
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Configuration.Binder": "9.0.4",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.4"
}
},
"Microsoft.Extensions.Logging.Console": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "YUzguHYlWfp4upfYlpVe3dnY59P25wc+/YLJ9/NQcblT3EvAB1CObQulClll7NtnFbbx4Js0a0UfyS8SbRsWXQ==",
+ "resolved": "9.0.4",
+ "contentHash": "cI0lQe0js65INCTCtAgnlVJWKgzgoRHVAW1B1zwCbmcliO4IZoTf92f1SYbLeLk7FzMJ/GlCvjLvJegJ6kltmQ==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging.Configuration": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1",
- "System.Text.Json": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging.Configuration": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4",
+ "System.Text.Json": "9.0.4"
}
},
"Microsoft.Extensions.Logging.Debug": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "pzdyibIV8k4sym0Sszcp2MJCuXrpOGs9qfOvY+hCRu8k4HbdVoeKOLnacxHK6vEPITX5o5FjjsZW2zScLXTjYA==",
+ "resolved": "9.0.4",
+ "contentHash": "D1jy+jy+huUUxnkZ0H480RZK8vqKn8NsQxYpMpPL/ALPPh1WATVLcr/uXI3RUBB45wMW5265O+hk9x3jnnXFuA==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4"
}
},
"Microsoft.Extensions.Logging.EventLog": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "+a4RlbwFWjsMujNNhf1Jy9Nm5CpMT+nxXxfgrkRSloPo0OAWhPSPsrFo6VWpvgIPPS41qmfAVWr3DqAmOoVZgQ==",
+ "resolved": "9.0.4",
+ "contentHash": "bApxdklf7QTsONOLR5ow6SdDFXR5ncHvumSEg2+QnCvxvkzc2z5kNn7yQCyupRLRN4jKbnlTkVX8x9qLlwL6Qg==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1",
- "System.Diagnostics.EventLog": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4",
+ "System.Diagnostics.EventLog": "9.0.4"
}
},
"Microsoft.Extensions.Logging.EventSource": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "d47ZRZUOg1dGOX+yisWScQ7w4+92OlR9beS2UXaiadUCA3RFoZzobzVgrzBX7Oo/qefx9LxdRcaeFpWKb3BNBw==",
+ "resolved": "9.0.4",
+ "contentHash": "R600zTxVJNw2IeAEOvdOJGNA1lHr1m3vo460hSF5G1DjwP0FNpyeH4lpLDMuf34diKwB1LTt5hBw1iF1/iuwsQ==",
"dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Logging": "9.0.1",
- "Microsoft.Extensions.Logging.Abstractions": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1",
- "Microsoft.Extensions.Primitives": "9.0.1",
- "System.Text.Json": "9.0.1"
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Logging": "9.0.4",
+ "Microsoft.Extensions.Logging.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4",
+ "Microsoft.Extensions.Primitives": "9.0.4",
+ "System.Text.Json": "9.0.4"
}
},
"Microsoft.Extensions.Options.ConfigurationExtensions": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "8RRKWtuU4fR+8MQLR/8CqZwZ9yc2xCpllw/WPRY7kskIqEq0hMcEI4AfUJO72yGiK2QJkrsDcUvgB5Yc+3+lyg==",
+ "resolved": "9.0.4",
+ "contentHash": "aridVhAT3Ep+vsirR1pzjaOw0Jwiob6dc73VFQn2XmDfBA2X98M8YKO1GarvsXRX7gX1Aj+hj2ijMzrMHDOm0A==",
"dependencies": {
- "Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
- "Microsoft.Extensions.Configuration.Binder": "9.0.1",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
- "Microsoft.Extensions.Options": "9.0.1",
- "Microsoft.Extensions.Primitives": "9.0.1"
+ "Microsoft.Extensions.Configuration.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Configuration.Binder": "9.0.4",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
+ "Microsoft.Extensions.Options": "9.0.4",
+ "Microsoft.Extensions.Primitives": "9.0.4"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "bHtTesA4lrSGD1ZUaMIx6frU3wyy0vYtTa/hM6gGQu5QNrydObv8T5COiGUWsisflAfmsaFOe9Xvw5NSO99z0g=="
+ "resolved": "9.0.4",
+ "contentHash": "SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA=="
},
"Microsoft.Web.WebView2": {
"type": "Transitive",
@@ -389,6 +502,11 @@
"resolved": "10.0.22621.756",
"contentHash": "7ZL2sFSioYm1Ry067Kw1hg0SCcW5kuVezC2SwjGbcPE61Nn+gTbH86T73G3LcEOVj0S3IZzNuE/29gZvOLS7VA=="
},
+ "NetSparkleUpdater.Chaos.NaCl": {
+ "type": "Transitive",
+ "resolved": "0.9.3",
+ "contentHash": "Copo3+rYuRVOuc6fmzHwXwehDC8l8DQ3y2VRI/d0sQTwProL4QAjkxRPV0zr3XBz1A8ZODXATOXV0hJXc7YdKg=="
+ },
"Semver": {
"type": "Transitive",
"resolved": "3.0.0",
@@ -397,6 +515,20 @@
"Microsoft.Extensions.Primitives": "5.0.1"
}
},
+ "Serilog": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "gmoWVOvKgbME8TYR+gwMf7osROiWAURterc6Rt2dQyX7wtjZYpqFiA/pY6ztjGQKKV62GGCyOcmtP1UKMHgSmA=="
+ },
+ "Serilog.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "9.0.0",
+ "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Logging": "9.0.0",
+ "Serilog": "4.2.0"
+ }
+ },
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "9.0.0",
@@ -404,13 +536,13 @@
},
"System.Diagnostics.DiagnosticSource": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "yOcDWx4P/s1I83+7gQlgQLmhny2eNcU0cfo1NBWi+en4EAI38Jau+/neT85gUW6w1s7+FUJc2qNOmmwGLIREqA=="
+ "resolved": "9.0.4",
+ "contentHash": "Be0emq8bRmcK4eeJIFUt9+vYPf7kzuQrFs8Ef1CdGvXpq/uSve22PTSkRF09bF/J7wmYJ2DHf2v7GaT3vMXnwQ=="
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ=="
+ "resolved": "9.0.4",
+ "contentHash": "getRQEXD8idlpb1KW56XuxImMy0FKp2WJPDf3Qr0kI/QKxxJSftqfDFVo0DZ3HCJRLU73qHSruv5q2l5O47jQQ=="
},
"System.Drawing.Common": {
"type": "Transitive",
@@ -422,8 +554,8 @@
},
"System.IO.Pipelines": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "uXf5o8eV/gtzDQY4lGROLFMWQvcViKcF8o4Q6KpIOjloAQXrnscQSu6gTxYJMHuNJnh7szIF9AzkaEq+zDLoEg=="
+ "resolved": "9.0.4",
+ "contentHash": "luF2Xba+lTe2GOoNQdZLe8q7K6s7nSpWZl9jIwWNMszN4/Yv0lmxk9HISgMmwdyZ83i3UhAGXaSY9o6IJBUuuA=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
@@ -435,16 +567,16 @@
},
"System.Text.Encodings.Web": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg=="
+ "resolved": "9.0.4",
+ "contentHash": "V+5cCPpk1S2ngekUs9nDrQLHGiWFZMg8BthADQr+Fwi59a8DdHFu26S2oi9Bfgv+d67bqmkPqctJXMEXiimXUg=="
},
"System.Text.Json": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "eqWHDZqYPv1PvuvoIIx5pF74plL3iEOZOl/0kQP+Y0TEbtgNnM2W6k8h8EPYs+LTJZsXuWa92n5W5sHTWvE3VA==",
+ "resolved": "9.0.4",
+ "contentHash": "pYtmpcO6R3Ef1XilZEHgXP2xBPVORbYEzRP7dl0IAAbN8Dm+kfwio8aCKle97rAWXOExr292MuxWYurIuwN62g==",
"dependencies": {
- "System.IO.Pipelines": "9.0.1",
- "System.Text.Encodings.Web": "9.0.1"
+ "System.IO.Pipelines": "9.0.4",
+ "System.Text.Encodings.Web": "9.0.4"
}
},
"Coder.Desktop.CoderSdk": {
@@ -496,13 +628,13 @@
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ=="
+ "resolved": "9.0.4",
+ "contentHash": "getRQEXD8idlpb1KW56XuxImMy0FKp2WJPDf3Qr0kI/QKxxJSftqfDFVo0DZ3HCJRLU73qHSruv5q2l5O47jQQ=="
},
"System.Text.Encodings.Web": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg=="
+ "resolved": "9.0.4",
+ "contentHash": "V+5cCPpk1S2ngekUs9nDrQLHGiWFZMg8BthADQr+Fwi59a8DdHFu26S2oi9Bfgv+d67bqmkPqctJXMEXiimXUg=="
}
},
"net8.0-windows10.0.19041/win-x64": {
@@ -528,13 +660,13 @@
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ=="
+ "resolved": "9.0.4",
+ "contentHash": "getRQEXD8idlpb1KW56XuxImMy0FKp2WJPDf3Qr0kI/QKxxJSftqfDFVo0DZ3HCJRLU73qHSruv5q2l5O47jQQ=="
},
"System.Text.Encodings.Web": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg=="
+ "resolved": "9.0.4",
+ "contentHash": "V+5cCPpk1S2ngekUs9nDrQLHGiWFZMg8BthADQr+Fwi59a8DdHFu26S2oi9Bfgv+d67bqmkPqctJXMEXiimXUg=="
}
},
"net8.0-windows10.0.19041/win-x86": {
@@ -560,13 +692,13 @@
},
"System.Diagnostics.EventLog": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "iVnDpgYJsRaRFnk77kcLA3+913WfWDtnAKrQl9tQ5ahqKANTaJKmQdsuPWWiAPWE9pk1Kj4Pg9JGXWfFYYyakQ=="
+ "resolved": "9.0.4",
+ "contentHash": "getRQEXD8idlpb1KW56XuxImMy0FKp2WJPDf3Qr0kI/QKxxJSftqfDFVo0DZ3HCJRLU73qHSruv5q2l5O47jQQ=="
},
"System.Text.Encodings.Web": {
"type": "Transitive",
- "resolved": "9.0.1",
- "contentHash": "XkspqduP2t1e1x2vBUAD/xZ5ZDvmywuUwsmB93MvyQLospJfqtX0GsR/kU0vUL2h4kmvf777z3txV2W4NrQ9Qg=="
+ "resolved": "9.0.4",
+ "contentHash": "V+5cCPpk1S2ngekUs9nDrQLHGiWFZMg8BthADQr+Fwi59a8DdHFu26S2oi9Bfgv+d67bqmkPqctJXMEXiimXUg=="
}
}
}
diff --git a/Coder.Desktop.sln b/Coder.Desktop.sln
index 0a20185..d1f5ac6 100644
--- a/Coder.Desktop.sln
+++ b/Coder.Desktop.sln
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.App", "Tests.App\Test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MutagenSdk", "MutagenSdk\MutagenSdk.csproj", "{E2477ADC-03DA-490D-9369-79A4CC4A58D2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.CoderSdk", "Tests.CoderSdk\Tests.CoderSdk.csproj", "{2BDEA023-FE75-476F-81DE-8EF90806C27C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -239,6 +241,22 @@ Global
{E2477ADC-03DA-490D-9369-79A4CC4A58D2}.Release|x64.Build.0 = Release|Any CPU
{E2477ADC-03DA-490D-9369-79A4CC4A58D2}.Release|x86.ActiveCfg = Release|Any CPU
{E2477ADC-03DA-490D-9369-79A4CC4A58D2}.Release|x86.Build.0 = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x64.Build.0 = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x86.Build.0 = Debug|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|ARM64.Build.0 = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x64.ActiveCfg = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x64.Build.0 = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x86.ActiveCfg = Release|Any CPU
+ {2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Coder.Desktop.sln.DotSettings b/Coder.Desktop.sln.DotSettings
index bf138c2..f524684 100644
--- a/Coder.Desktop.sln.DotSettings
+++ b/Coder.Desktop.sln.DotSettings
@@ -5,6 +5,7 @@
True
True
True
+ True
<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
<TypePattern DisplayName="Non-reorderable types" Priority="99999999">
<TypePattern.Match>
diff --git a/CoderSdk/Agent/AgentApiClient.cs b/CoderSdk/Agent/AgentApiClient.cs
new file mode 100644
index 0000000..27eaea3
--- /dev/null
+++ b/CoderSdk/Agent/AgentApiClient.cs
@@ -0,0 +1,61 @@
+using System.Text.Json.Serialization;
+
+namespace Coder.Desktop.CoderSdk.Agent;
+
+public interface IAgentApiClientFactory
+{
+ public IAgentApiClient Create(string hostname);
+}
+
+public class AgentApiClientFactory : IAgentApiClientFactory
+{
+ public IAgentApiClient Create(string hostname)
+ {
+ return new AgentApiClient(hostname);
+ }
+}
+
+public partial interface IAgentApiClient
+{
+}
+
+[JsonSerializable(typeof(ListDirectoryRequest))]
+[JsonSerializable(typeof(ListDirectoryResponse))]
+[JsonSerializable(typeof(Response))]
+public partial class AgentApiJsonContext : JsonSerializerContext;
+
+public partial class AgentApiClient : IAgentApiClient
+{
+ private const int AgentApiPort = 4;
+
+ private readonly JsonHttpClient _httpClient;
+
+ public AgentApiClient(string hostname) : this(new UriBuilder
+ {
+ Scheme = "http",
+ Host = hostname,
+ Port = AgentApiPort,
+ Path = "/",
+ }.Uri)
+ {
+ }
+
+ public AgentApiClient(Uri baseUrl)
+ {
+ if (baseUrl.PathAndQuery != "/")
+ throw new ArgumentException($"Base URL '{baseUrl}' must not contain a path", nameof(baseUrl));
+ _httpClient = new JsonHttpClient(baseUrl, AgentApiJsonContext.Default);
+ }
+
+ private async Task SendRequestNoBodyAsync(HttpMethod method, string path,
+ CancellationToken ct = default)
+ {
+ return await SendRequestAsync