1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

UI - Rename each OS assembly to Artemis 2

UI - Added auto-run
UI - Added auto-update
This commit is contained in:
Robert 2022-08-07 14:15:18 +02:00
parent 20af5e9c21
commit faf5302975
30 changed files with 1568 additions and 192 deletions

View File

@ -67,6 +67,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprofiling/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprofiling/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cscriptingproviders_005Cscripts/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cscriptingproviders_005Cscripts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=providers_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer_005Cinterfaces/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>

View File

@ -0,0 +1,23 @@
using Artemis.Core.SkiaSharp;
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a class that can provide one or more graphics <see cref="IManagedGraphicsContext" /> instances by name.
/// </summary>
public interface IGraphicsContextProvider
{
/// <summary>
/// Gets the name of the graphics context.
/// </summary>
string GraphicsContextName { get; }
/// <summary>
/// Creates an instance of the managed graphics context this provider provides.
/// </summary>
/// <returns>
/// An instance of the resulting managed graphics context if successfully created; otherwise
/// <see langword="null" />.
/// </returns>
IManagedGraphicsContext? GetGraphicsContext();
}

View File

@ -1,22 +0,0 @@
using System.Collections.Generic;
using Artemis.Core.SkiaSharp;
namespace Artemis.Core.Services;
/// <summary>
/// Represents a class that can provide one or more graphics <see cref="IManagedGraphicsContext"/> instances by name.
/// </summary>
public interface IGraphicsContextProvider
{
/// <summary>
/// Gets a read only collection containing the names of all the graphics contexts supported by this provider.
/// </summary>
IReadOnlyCollection<string> GraphicsContextNames { get; }
/// <summary>
/// Gets a managed graphics context by name.
/// </summary>
/// <param name="name">The name of the graphics context.</param>
/// <returns>If found, an instance of the managed graphics context with the given <paramref name="name"/>; otherwise <see langword="null"/>.</returns>
IManagedGraphicsContext? GetGraphicsContext(string name);
}

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services.Models; using Artemis.Core.Services.Models;
using Artemis.Core.SkiaSharp; using Artemis.Core.SkiaSharp;
using Artemis.Storage.Entities.Surface; using Artemis.Storage.Entities.Surface;
@ -363,15 +364,15 @@ namespace Artemis.Core.Services
return; return;
} }
IGraphicsContextProvider? provider = _kernel.TryGet<IGraphicsContextProvider>(); List<IGraphicsContextProvider> providers = _kernel.Get<List<IGraphicsContextProvider>>();
if (provider == null) if (!providers.Any())
{ {
_logger.Warning("No graphics context provider found, defaulting to software rendering"); _logger.Warning("No graphics context provider found, defaulting to software rendering");
UpdateGraphicsContext(null); UpdateGraphicsContext(null);
return; return;
} }
IManagedGraphicsContext? context = provider.GetGraphicsContext(_preferredGraphicsContext.Value); IManagedGraphicsContext? context = providers.FirstOrDefault(p => p.GraphicsContextName == _preferredGraphicsContext.Value)?.GetGraphicsContext();
if (context == null) if (context == null)
{ {
_logger.Warning("No graphics context named '{Context}' found, defaulting to software rendering", _preferredGraphicsContext.Value); _logger.Warning("No graphics context named '{Context}' found, defaulting to software rendering", _preferredGraphicsContext.Value);

View File

@ -5,21 +5,24 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<OutputPath>bin</OutputPath>
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
<AssemblyName>Artemis 2</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**"/> <AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore"/> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15"/> <PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15"/> <PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15"/> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15"/> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="ReactiveUI" Version="17.1.50"/> <PackageReference Include="ReactiveUI" Version="17.1.50" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj"/> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj"/> <ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -5,21 +5,24 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<OutputPath>bin</OutputPath>
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
<AssemblyName>Artemis 2</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**"/> <AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore"/> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15"/> <PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15"/> <PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15"/> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15"/> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="ReactiveUI" Version="17.1.50"/> <PackageReference Include="ReactiveUI" Version="17.1.50" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj"/> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj"/> <ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,21 @@
using System.Threading.Tasks;
namespace Artemis.UI.Shared.Providers;
/// <summary>
/// Represents a provider for custom cursors.
/// </summary>
public interface IAutoRunProvider
{
/// <summary>
/// Asynchronously enables auto-run.
/// </summary>
/// <param name="recreate">A boolean indicating whether the auto-run configuration should be recreated (the auto run delay changed)</param>
/// <param name="autoRunDelay">The delay in seconds before the application should start (if supported)</param>
Task EnableAutoRun(bool recreate, int autoRunDelay);
/// <summary>
/// Asynchronously disables auto-run.
/// </summary>
Task DisableAutoRun();
}

View File

@ -0,0 +1,31 @@
using System.Threading.Tasks;
namespace Artemis.UI.Shared.Providers;
/// <summary>
/// Represents a provider for custom cursors.
/// </summary>
public interface IUpdateProvider
{
/// <summary>
/// Asynchronously checks whether an update is available.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <returns>A task returning <see langword="true" /> if an update is available; otherwise <see langword="false" />.</returns>
Task<bool> CheckForUpdate(string channel);
/// <summary>
/// Applies any available updates.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <param name="silent">Whether or not to update silently.</param>
Task ApplyUpdate(string channel, bool silent);
/// <summary>
/// Offer to install the update to the user.
/// </summary>
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
/// <param name="windowOpen">A boolean indicating whether the main window is open.</param>
/// <returns>A task returning <see langword="true" /> if the user chose to update; otherwise <see langword="false" />.</returns>
Task OfferUpdate(string channel, bool windowOpen);
}

View File

@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="Artemis.UI.Shared.Services.ExceptionDialogView" x:Class="Artemis.UI.Shared.Services.ExceptionDialogView"
Icon="/Assets/Images/Logo/application.ico"
Title="{Binding Title}" Title="{Binding Title}"
ExtendClientAreaToDecorationsHint="True" ExtendClientAreaToDecorationsHint="True"
Width="800" Width="800"

View File

@ -1,5 +1,6 @@
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Windows.Ninject; using Artemis.UI.Windows.Ninject;
using Artemis.UI.Windows.Providers;
using Artemis.UI.Windows.Providers.Input; using Artemis.UI.Windows.Providers.Input;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;

View File

@ -1,42 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<OutputPath>bin</OutputPath>
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
<Company>Artemis RGB</Company>
<Product>Artemis 2.0</Product>
<ApplicationIcon>..\Artemis.UI\Assets\Images\Logo\application.ico</ApplicationIcon>
<AssemblyName>Artemis 2</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**"/> <AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore"/> <None Remove=".gitignore" />
<None Remove="Assets\Cursors\aero_crosshair.cur"/> <None Remove="Assets\Cursors\aero_crosshair.cur" />
<None Remove="Assets\Cursors\aero_crosshair_minus.cur"/> <None Remove="Assets\Cursors\aero_crosshair_minus.cur" />
<None Remove="Assets\Cursors\aero_crosshair_plus.cur"/> <None Remove="Assets\Cursors\aero_crosshair_plus.cur" />
<None Remove="Assets\Cursors\aero_drag.cur"/> <None Remove="Assets\Cursors\aero_drag.cur" />
<None Remove="Assets\Cursors\aero_drag_ew.cur"/> <None Remove="Assets\Cursors\aero_drag_ew.cur" />
<None Remove="Assets\Cursors\aero_fill.cur"/> <None Remove="Assets\Cursors\aero_fill.cur" />
<None Remove="Assets\Cursors\aero_pen_min.cur"/> <None Remove="Assets\Cursors\aero_pen_min.cur" />
<None Remove="Assets\Cursors\aero_pen_plus.cur"/> <None Remove="Assets\Cursors\aero_pen_plus.cur" />
<None Remove="Assets\Cursors\aero_rotate.cur"/> <None Remove="Assets\Cursors\aero_rotate.cur" />
<None Remove="Assets\Cursors\aero_rotate_bl.cur"/> <None Remove="Assets\Cursors\aero_rotate_bl.cur" />
<None Remove="Assets\Cursors\aero_rotate_br.cur"/> <None Remove="Assets\Cursors\aero_rotate_br.cur" />
<None Remove="Assets\Cursors\aero_rotate_tl.cur"/> <None Remove="Assets\Cursors\aero_rotate_tl.cur" />
<None Remove="Assets\Cursors\aero_rotate_tr.cur"/> <None Remove="Assets\Cursors\aero_rotate_tr.cur" />
<None Include="..\Artemis.UI\Assets\Images\Logo\application.ico">
<Link>application.ico</Link>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15"/> <PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15"/> <PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15"/> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15"/> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="Avalonia.Win32" Version="0.10.15"/> <PackageReference Include="Avalonia.Win32" Version="0.10.15" />
<PackageReference Include="Microsoft.Win32" Version="2.0.1"/> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageReference Include="RawInput.Sharp" Version="0.0.4"/> <PackageReference Include="Microsoft.Win32" Version="2.0.1" />
<PackageReference Include="ReactiveUI" Version="17.1.50"/> <PackageReference Include="RawInput.Sharp" Version="0.0.4" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.1-preview.1"/> <PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.1-preview.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj"/> <ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj"/> <ProjectReference Include="..\Artemis.UI\Artemis.UI.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

View File

@ -0,0 +1,279 @@
#nullable disable
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Artemis.UI.Windows.Models
{
public class DevOpsBuilds
{
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("value")]
public List<DevOpsBuild> Builds { get; set; }
}
public class DevOpsBuild
{
[JsonProperty("_links")]
public BuildLinks Links { get; set; }
[JsonProperty("properties")]
public Properties Properties { get; set; }
[JsonProperty("tags")]
public List<object> Tags { get; set; }
[JsonProperty("validationResults")]
public List<object> ValidationResults { get; set; }
[JsonProperty("plans")]
public List<Plan> Plans { get; set; }
[JsonProperty("triggerInfo")]
public TriggerInfo TriggerInfo { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("buildNumber")]
public string BuildNumber { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("result")]
public string Result { get; set; }
[JsonProperty("queueTime")]
public DateTimeOffset QueueTime { get; set; }
[JsonProperty("startTime")]
public DateTimeOffset StartTime { get; set; }
[JsonProperty("finishTime")]
public DateTimeOffset FinishTime { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("definition")]
public Definition Definition { get; set; }
[JsonProperty("buildNumberRevision")]
public long BuildNumberRevision { get; set; }
[JsonProperty("project")]
public Project Project { get; set; }
[JsonProperty("uri")]
public string Uri { get; set; }
[JsonProperty("sourceBranch")]
public string SourceBranch { get; set; }
[JsonProperty("sourceVersion")]
public string SourceVersion { get; set; }
[JsonProperty("priority")]
public string Priority { get; set; }
[JsonProperty("reason")]
public string Reason { get; set; }
[JsonProperty("requestedFor")]
public LastChangedBy RequestedFor { get; set; }
[JsonProperty("requestedBy")]
public LastChangedBy RequestedBy { get; set; }
[JsonProperty("lastChangedDate")]
public DateTimeOffset LastChangedDate { get; set; }
[JsonProperty("lastChangedBy")]
public LastChangedBy LastChangedBy { get; set; }
[JsonProperty("orchestrationPlan")]
public Plan OrchestrationPlan { get; set; }
[JsonProperty("logs")]
public Logs Logs { get; set; }
[JsonProperty("repository")]
public Repository Repository { get; set; }
[JsonProperty("keepForever")]
public bool KeepForever { get; set; }
[JsonProperty("retainedByRelease")]
public bool RetainedByRelease { get; set; }
[JsonProperty("triggeredByBuild")]
public object TriggeredByBuild { get; set; }
}
public class Definition
{
[JsonProperty("drafts")]
public List<object> Drafts { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("uri")]
public string Uri { get; set; }
[JsonProperty("path")]
public string Path { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("queueStatus")]
public string QueueStatus { get; set; }
[JsonProperty("revision")]
public long Revision { get; set; }
[JsonProperty("project")]
public Project Project { get; set; }
}
public class Project
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("state")]
public string State { get; set; }
[JsonProperty("revision")]
public long Revision { get; set; }
[JsonProperty("visibility")]
public string Visibility { get; set; }
[JsonProperty("lastUpdateTime")]
public DateTimeOffset LastUpdateTime { get; set; }
}
public class LastChangedBy
{
[JsonProperty("displayName")]
public string DisplayName { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("_links")]
public LastChangedByLinks Links { get; set; }
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("uniqueName")]
public object UniqueName { get; set; }
[JsonProperty("imageUrl")]
public object ImageUrl { get; set; }
[JsonProperty("descriptor")]
public string Descriptor { get; set; }
}
public class LastChangedByLinks
{
[JsonProperty("avatar")]
public Badge Avatar { get; set; }
}
public class Badge
{
[JsonProperty("href")]
public Uri Href { get; set; }
}
public class BuildLinks
{
[JsonProperty("self")]
public Badge Self { get; set; }
[JsonProperty("web")]
public Badge Web { get; set; }
[JsonProperty("sourceVersionDisplayUri")]
public Badge SourceVersionDisplayUri { get; set; }
[JsonProperty("timeline")]
public Badge Timeline { get; set; }
[JsonProperty("badge")]
public Badge Badge { get; set; }
}
public class Logs
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
}
public class Plan
{
[JsonProperty("planId")]
public Guid PlanId { get; set; }
}
public class Properties
{
}
public class Repository
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("clean")]
public object Clean { get; set; }
[JsonProperty("checkoutSubmodules")]
public bool CheckoutSubmodules { get; set; }
}
public class TriggerInfo
{
[JsonProperty("ci.sourceBranch")]
public string CiSourceBranch { get; set; }
[JsonProperty("ci.sourceSha")]
public string CiSourceSha { get; set; }
[JsonProperty("ci.message")]
public string CiMessage { get; set; }
[JsonProperty("ci.triggerRepository")]
public string CiTriggerRepository { get; set; }
}
}

View File

@ -0,0 +1,244 @@
#nullable disable
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Artemis.UI.Windows.Models
{
public class GitHubDifference
{
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("html_url")]
public Uri HtmlUrl { get; set; }
[JsonProperty("permalink_url")]
public Uri PermalinkUrl { get; set; }
[JsonProperty("diff_url")]
public Uri DiffUrl { get; set; }
[JsonProperty("patch_url")]
public Uri PatchUrl { get; set; }
[JsonProperty("base_commit")]
public BaseCommitClass BaseCommit { get; set; }
[JsonProperty("merge_base_commit")]
public BaseCommitClass MergeBaseCommit { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("ahead_by")]
public long AheadBy { get; set; }
[JsonProperty("behind_by")]
public long BehindBy { get; set; }
[JsonProperty("total_commits")]
public long TotalCommits { get; set; }
[JsonProperty("commits")]
public List<BaseCommitClass> Commits { get; set; }
[JsonProperty("files")]
public List<File> Files { get; set; }
}
public class BaseCommitClass
{
[JsonProperty("sha")]
public string Sha { get; set; }
[JsonProperty("node_id")]
public string NodeId { get; set; }
[JsonProperty("commit")]
public BaseCommitCommit Commit { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("html_url")]
public Uri HtmlUrl { get; set; }
[JsonProperty("comments_url")]
public Uri CommentsUrl { get; set; }
[JsonProperty("author")]
public BaseCommitAuthor Author { get; set; }
[JsonProperty("committer")]
public BaseCommitAuthor Committer { get; set; }
[JsonProperty("parents")]
public List<Parent> Parents { get; set; }
}
public class BaseCommitAuthor
{
[JsonProperty("login")]
public string Login { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("node_id")]
public string NodeId { get; set; }
[JsonProperty("avatar_url")]
public Uri AvatarUrl { get; set; }
[JsonProperty("gravatar_id")]
public string GravatarId { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("html_url")]
public Uri HtmlUrl { get; set; }
[JsonProperty("followers_url")]
public Uri FollowersUrl { get; set; }
[JsonProperty("following_url")]
public string FollowingUrl { get; set; }
[JsonProperty("gists_url")]
public string GistsUrl { get; set; }
[JsonProperty("starred_url")]
public string StarredUrl { get; set; }
[JsonProperty("subscriptions_url")]
public Uri SubscriptionsUrl { get; set; }
[JsonProperty("organizations_url")]
public Uri OrganizationsUrl { get; set; }
[JsonProperty("repos_url")]
public Uri ReposUrl { get; set; }
[JsonProperty("events_url")]
public string EventsUrl { get; set; }
[JsonProperty("received_events_url")]
public Uri ReceivedEventsUrl { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("site_admin")]
public bool SiteAdmin { get; set; }
}
public class BaseCommitCommit
{
[JsonProperty("author")]
public PurpleAuthor Author { get; set; }
[JsonProperty("committer")]
public PurpleAuthor Committer { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("tree")]
public Tree Tree { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("comment_count")]
public long CommentCount { get; set; }
[JsonProperty("verification")]
public Verification Verification { get; set; }
}
public class PurpleAuthor
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("date")]
public DateTimeOffset Date { get; set; }
}
public class Tree
{
[JsonProperty("sha")]
public string Sha { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
}
public class Verification
{
[JsonProperty("verified")]
public bool Verified { get; set; }
[JsonProperty("reason")]
public string Reason { get; set; }
[JsonProperty("signature")]
public string Signature { get; set; }
[JsonProperty("payload")]
public string Payload { get; set; }
}
public class Parent
{
[JsonProperty("sha")]
public string Sha { get; set; }
[JsonProperty("url")]
public Uri Url { get; set; }
[JsonProperty("html_url")]
public Uri HtmlUrl { get; set; }
}
public class File
{
[JsonProperty("sha")]
public string Sha { get; set; }
[JsonProperty("filename")]
public string Filename { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("additions")]
public long Additions { get; set; }
[JsonProperty("deletions")]
public long Deletions { get; set; }
[JsonProperty("changes")]
public long Changes { get; set; }
[JsonProperty("blob_url")]
public Uri BlobUrl { get; set; }
[JsonProperty("raw_url")]
public Uri RawUrl { get; set; }
[JsonProperty("contents_url")]
public Uri ContentsUrl { get; set; }
[JsonProperty("patch")]
public string Patch { get; set; }
[JsonProperty("previous_filename", NullValueHandling = NullValueHandling.Ignore)]
public string PreviousFilename { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using Artemis.Core.Services; using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared.Providers; using Artemis.UI.Shared.Providers;
using Artemis.UI.Windows.Providers; using Artemis.UI.Windows.Providers;
using Ninject.Modules; using Ninject.Modules;
@ -14,6 +15,8 @@ public class WindowsModule : NinjectModule
{ {
Kernel!.Bind<ICursorProvider>().To<CursorProvider>().InSingletonScope(); Kernel!.Bind<ICursorProvider>().To<CursorProvider>().InSingletonScope();
Kernel!.Bind<IGraphicsContextProvider>().To<GraphicsContextProvider>().InSingletonScope(); Kernel!.Bind<IGraphicsContextProvider>().To<GraphicsContextProvider>().InSingletonScope();
Kernel!.Bind<IUpdateProvider>().To<UpdateProvider>().InSingletonScope();
Kernel!.Bind<IAutoRunProvider>().To<AutoRunProvider>();
} }
#endregion #endregion

View File

@ -0,0 +1,126 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Artemis.Core;
using Artemis.UI.Shared.Providers;
using Avalonia.Platform;
namespace Artemis.UI.Windows.Providers;
public class AutoRunProvider : IAutoRunProvider
{
private readonly IAssetLoader _assetLoader;
public AutoRunProvider(IAssetLoader assetLoader)
{
_assetLoader = assetLoader;
}
private async Task<bool> IsAutoRunTaskCreated()
{
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/TN \"Artemis 2 autorun\""
}
};
schtasks.Start();
await schtasks.WaitForExitAsync();
return schtasks.ExitCode == 0;
}
private async Task CreateAutoRunTask(TimeSpan autoRunDelay)
{
await using Stream taskFile = _assetLoader.Open(new Uri("avares://Artemis 2/Assets/autorun.xml"));
XDocument document = await XDocument.LoadAsync(taskFile, LoadOptions.None, CancellationToken.None);
XElement task = document.Descendants().First();
task.Descendants().First(d => d.Name.LocalName == "RegistrationInfo").Descendants().First(d => d.Name.LocalName == "Date")
.SetValue(DateTime.Now);
task.Descendants().First(d => d.Name.LocalName == "RegistrationInfo").Descendants().First(d => d.Name.LocalName == "Author")
.SetValue(WindowsIdentity.GetCurrent().Name);
task.Descendants().First(d => d.Name.LocalName == "Triggers").Descendants().First(d => d.Name.LocalName == "LogonTrigger").Descendants().First(d => d.Name.LocalName == "Delay")
.SetValue(autoRunDelay);
task.Descendants().First(d => d.Name.LocalName == "Principals").Descendants().First(d => d.Name.LocalName == "Principal").Descendants().First(d => d.Name.LocalName == "UserId")
.SetValue(WindowsIdentity.GetCurrent().User!.Value);
task.Descendants().First(d => d.Name.LocalName == "Actions").Descendants().First(d => d.Name.LocalName == "Exec").Descendants().First(d => d.Name.LocalName == "WorkingDirectory")
.SetValue(Constants.ApplicationFolder);
task.Descendants().First(d => d.Name.LocalName == "Actions").Descendants().First(d => d.Name.LocalName == "Exec").Descendants().First(d => d.Name.LocalName == "Command")
.SetValue("\"" + Constants.ExecutablePath + "\"");
string xmlPath = Path.GetTempFileName();
await using (Stream fileStream = new FileStream(xmlPath, FileMode.Create))
await document.SaveAsync(fileStream, SaveOptions.None, CancellationToken.None);
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = $"/Create /XML \"{xmlPath}\" /tn \"Artemis 2 autorun\" /F"
}
};
schtasks.Start();
await schtasks.WaitForExitAsync();
File.Delete(xmlPath);
}
private async Task RemoveAutoRunTask()
{
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/Delete /TN \"Artemis 2 autorun\" /f"
}
};
schtasks.Start();
await schtasks.WaitForExitAsync();
}
/// <inheritdoc />
public async Task EnableAutoRun(bool recreate, int autoRunDelay)
{
// if (Constants.BuildInfo.IsLocalBuild)
// return;
// Create or remove the task if necessary
bool taskCreated = false;
if (!recreate)
taskCreated = await IsAutoRunTaskCreated();
if (!taskCreated)
await CreateAutoRunTask(TimeSpan.FromSeconds(autoRunDelay));
}
/// <inheritdoc />
public async Task DisableAutoRun()
{
bool taskCreated = await IsAutoRunTaskCreated();
if (taskCreated)
await RemoveAutoRunTask();
}
}

View File

@ -11,9 +11,9 @@ public class CursorProvider : ICursorProvider
{ {
public CursorProvider(IAssetLoader assetLoader) public CursorProvider(IAssetLoader assetLoader)
{ {
Rotate = new Cursor(new Bitmap(assetLoader.Open(new Uri("avares://Artemis.UI.Windows/Assets/Cursors/aero_rotate.png"))), new PixelPoint(21, 10)); Rotate = new Cursor(new Bitmap(assetLoader.Open(new Uri("avares://Artemis 2/Assets/Cursors/aero_rotate.png"))), new PixelPoint(21, 10));
Drag = new Cursor(new Bitmap(assetLoader.Open(new Uri("avares://Artemis.UI.Windows/Assets/Cursors/aero_drag.png"))), new PixelPoint(11, 3)); Drag = new Cursor(new Bitmap(assetLoader.Open(new Uri("avares://Artemis 2/Assets/Cursors/aero_drag.png"))), new PixelPoint(11, 3));
DragHorizontal = new Cursor(new Bitmap(assetLoader.Open(new Uri("avares://Artemis.UI.Windows/Assets/Cursors/aero_drag_horizontal.png"))), new PixelPoint(16, 5)); DragHorizontal = new Cursor(new Bitmap(assetLoader.Open(new Uri("avares://Artemis 2/Assets/Cursors/aero_drag_horizontal.png"))), new PixelPoint(16, 5));
} }
public Cursor Rotate { get; } public Cursor Rotate { get; }

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Core.SkiaSharp; using Artemis.Core.SkiaSharp;
using Artemis.UI.Windows.SkiaSharp; using Artemis.UI.Windows.SkiaSharp;
@ -9,18 +8,11 @@ public class GraphicsContextProvider : IGraphicsContextProvider
{ {
private VulkanContext? _vulkanContext; private VulkanContext? _vulkanContext;
/// <inheritdoc /> public string GraphicsContextName => "Vulkan";
public IReadOnlyCollection<string> GraphicsContextNames => new List<string> {"Vulkan"}.AsReadOnly();
/// <inheritdoc /> public IManagedGraphicsContext? GetGraphicsContext()
public IManagedGraphicsContext? GetGraphicsContext(string name)
{ {
if (name == "Vulkan") _vulkanContext ??= new VulkanContext();
{ return _vulkanContext;
_vulkanContext ??= new VulkanContext();
return _vulkanContext;
}
return null;
} }
} }

View File

@ -0,0 +1,216 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Artemis.UI.Exceptions;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.MainWindow;
using Artemis.UI.Windows.Models;
using Artemis.UI.Windows.Screens.Update;
using Avalonia.Threading;
using Flurl;
using Flurl.Http;
using Microsoft.Toolkit.Uwp.Notifications;
using Serilog;
using File = System.IO.File;
namespace Artemis.UI.Windows.Providers;
public class UpdateProvider : IUpdateProvider, IDisposable
{
private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/";
private const string InstallerUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe";
private readonly ILogger _logger;
private readonly IWindowService _windowService;
private readonly IMainWindowService _mainWindowService;
public UpdateProvider(ILogger logger, IWindowService windowService, IMainWindowService mainWindowService)
{
_logger = logger;
_windowService = windowService;
_mainWindowService = mainWindowService;
ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated;
}
private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e)
{
ToastArguments args = ToastArguments.Parse(e.Argument);
string channel = args.Get("channel");
string action = "view-changes";
if (args.Contains("action"))
action = args.Get("action");
if (action == "install")
await RunInstaller(channel, true);
else if (action == "view-changes")
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
_mainWindowService.OpenMainWindow();
await OfferUpdate(channel, true);
});
}
}
private async Task RunInstaller(string channel, bool silent)
{
_logger.Information("ApplyUpdate: Applying update");
// Ensure the installer is up-to-date, get installer build info
DevOpsBuild? buildInfo = await GetBuildInfo(6);
string installerPath = Path.Combine(Core.Constants.DataFolder, "installer", "Artemis.Installer.exe");
// Always update installer if it is missing ^^
if (!File.Exists(installerPath))
await UpdateInstaller();
// Compare the creation date of the installer with the build date and update if needed
else
{
if (buildInfo != null && File.GetLastWriteTime(installerPath) < buildInfo.FinishTime)
await UpdateInstaller();
}
_logger.Information("ApplyUpdate: Running installer at {InstallerPath}", installerPath);
try
{
Process.Start(new ProcessStartInfo(installerPath, "-autoupdate")
{
UseShellExecute = true,
Verb = "runas"
});
}
catch (Win32Exception e)
{
if (e.NativeErrorCode == 0x4c7)
_logger.Warning("ApplyUpdate: Operation was cancelled, user likely clicked No in UAC dialog");
else
throw;
}
}
private async Task UpdateInstaller()
{
string installerDirectory = Path.Combine(Core.Constants.DataFolder, "installer");
string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe");
_logger.Information("UpdateInstaller: Downloading installer from {DownloadUrl}", InstallerUrl);
using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(InstallerUrl);
if (!httpResponseMessage.IsSuccessStatusCode)
throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}");
_logger.Information("UpdateInstaller: Writing installer file to {InstallerPath}", installerPath);
if (File.Exists(installerPath))
File.Delete(installerPath);
Core.Utilities.CreateAccessibleDirectory(installerDirectory);
await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None);
await httpResponseMessage.Content.CopyToAsync(fs);
}
/// <inheritdoc />
public async Task<bool> CheckForUpdate(string channel)
{
DevOpsBuild? buildInfo = await GetBuildInfo(1);
if (buildInfo == null)
return false;
double buildNumber = double.Parse(buildInfo.BuildNumber, CultureInfo.InvariantCulture);
string buildNumberDisplay = buildNumber.ToString(CultureInfo.InvariantCulture);
_logger.Information("Latest build is {BuildNumber}, we're running {LocalBuildNumber}", buildNumberDisplay, Core.Constants.BuildInfo.BuildNumberDisplay);
return buildNumber > Core.Constants.BuildInfo.BuildNumber;
}
/// <inheritdoc />
public async Task ApplyUpdate(string channel, bool silent)
{
await RunInstaller(channel, silent);
}
/// <inheritdoc />
public async Task OfferUpdate(string channel, bool windowOpen)
{
if (windowOpen)
{
bool update = await _windowService.ShowDialogAsync<UpdateDialogViewModel, bool>(("channel", channel));
if (update)
await RunInstaller(channel, false);
}
else
{
ShowDesktopNotification(channel);
}
}
private void ShowDesktopNotification(string channel)
{
new ToastContentBuilder()
.AddArgument("channel", channel)
.AddText("An update is available")
.AddButton(new ToastButton().SetContent("Install").AddArgument("action", "install").SetBackgroundActivation())
.AddButton(new ToastButton().SetContent("View changes").AddArgument("action", "view-changes"))
.AddHeroImage(new Uri(@"C:\Repos\Artemis\src\Artemis.UI\Assets\Images\home-banner.png"))
.Show();
}
public async Task<DevOpsBuild?> GetBuildInfo(int buildDefinition, string? buildNumber = null)
{
Url request = ApiUrl.AppendPathSegments("build", "builds")
.SetQueryParam("definitions", buildDefinition)
.SetQueryParam("resultFilter", "succeeded")
.SetQueryParam("$top", 1)
.SetQueryParam("api-version", "6.1-preview.6");
if (buildNumber != null)
request = request.SetQueryParam("buildNumber", buildNumber);
try
{
DevOpsBuilds result = await request.GetJsonAsync<DevOpsBuilds>();
try
{
return result.Builds.FirstOrDefault();
}
catch (Exception e)
{
_logger.Warning(e, "GetBuildInfo: Failed to retrieve build info JSON");
throw;
}
}
catch (FlurlHttpException e)
{
_logger.Warning("GetBuildInfo: Getting build info, request returned {StatusCode}", e.StatusCode);
throw;
}
}
public async Task<GitHubDifference> GetBuildDifferences(DevOpsBuild a, DevOpsBuild b)
{
return await "https://api.github.com"
.AppendPathSegments("repos", "Artemis-RGB", "Artemis", "compare")
.AppendPathSegment(a.SourceVersion + "..." + b.SourceVersion)
.WithHeader("User-Agent", "Artemis 2")
.WithHeader("Accept", "application/vnd.github.v3+json")
.GetJsonAsync<GitHubDifference>();
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
ToastNotificationManagerCompat.OnActivated -= ToastNotificationManagerCompatOnOnActivated;
ToastNotificationManagerCompat.Uninstall();
}
#endregion
}

View File

@ -0,0 +1,70 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:update="clr-namespace:Artemis.UI.Windows.Screens.Update"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Windows.Screens.Update.UpdateDialogView"
x:DataType="update:UpdateDialogViewModel"
Title="Artemis | Update available"
Width="750"
MinWidth="750"
Height="500"
WindowStartupLocation="CenterOwner">
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
<TextBlock Grid.Row="0" Classes="h4">
A new Artemis update is available! 🥳
</TextBlock>
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding RetrievingChanges}" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock>Retrieving changes...</TextBlock>
<ProgressBar IsIndeterminate="True"></ProgressBar>
</StackPanel>
<Border Grid.Row="1" Classes="card" IsVisible="{CompiledBinding !RetrievingChanges}">
<Grid RowDefinitions="Auto,*">
<StackPanel Grid.Row="0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="You are currently running build " />
<TextBlock Text="{CompiledBinding CurrentBuild, Mode=OneWay}"></TextBlock>
<TextBlock Text=" while the latest build is " />
<TextBlock Text="{CompiledBinding LatestBuild, Mode=OneWay}"></TextBlock>
<TextBlock Text="." />
</StackPanel>
<TextBlock Text="Updating Artemis will give you the latest bug(fixes), features and improvements." />
<Separator Classes="card-separator" />
<TextBlock Classes="h5" IsVisible="{CompiledBinding HasChanges}">
Changelog (auto-generated)
</TextBlock>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalAlignment="Top" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" IsVisible="{CompiledBinding HasChanges}">
<ItemsControl Items="{CompiledBinding Changes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="Auto,*">
<TextBlock Text="•" Margin="10 0" />
<TextBlock Grid.Column="1" Text="{Binding}" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding !HasChanges}" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center">We couldn't retrieve any changes</TextBlock>
<controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis/commits/master" HorizontalAlignment="Center">View online</controls:HyperlinkButton>
</StackPanel>
</Grid>
</Border>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Grid.Row="2" Margin="0 15 0 0">
<Button Classes="accent" Command="{CompiledBinding Install}">Install update</Button>
<Button Command="{CompiledBinding AskLater}">Ask later</Button>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,22 @@
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Windows.Screens.Update;
public partial class UpdateDialogView : ReactiveCoreWindow<UpdateDialogViewModel>
{
public UpdateDialogView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Windows.Models;
using Artemis.UI.Windows.Providers;
using Avalonia.Threading;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Windows.Screens.Update;
public class UpdateDialogViewModel : DialogViewModelBase<bool>
{
private readonly UpdateProvider _updateProvider;
private readonly INotificationService _notificationService;
// Based on https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#skipping-ci-for-individual-commits
private readonly string[] _excludedCommitMessages =
{
"[skip ci]",
"[ci skip]",
"skip-checks: true",
"skip-checks:true",
"[skip azurepipelines]",
"[azurepipelines skip]",
"[skip azpipelines]",
"[azpipelines skip]",
"[skip azp]",
"[azp skip]",
"***NO_CI***"
};
private bool _retrievingChanges;
private bool _hasChanges;
private string? _latestBuild;
public UpdateDialogViewModel(string channel, IUpdateProvider updateProvider, INotificationService notificationService)
{
_updateProvider = (UpdateProvider) updateProvider;
_notificationService = notificationService;
Channel = channel;
CurrentBuild = Core.Constants.BuildInfo.BuildNumberDisplay;
this.WhenActivated((CompositeDisposable _) => Dispatcher.UIThread.InvokeAsync(GetBuildChanges));
Install = ReactiveCommand.Create(() => Close(true));
AskLater = ReactiveCommand.Create(() => Close(false));
}
public ReactiveCommand<Unit, Unit> Install { get; }
public ReactiveCommand<Unit, Unit> AskLater { get; }
public string Channel { get; }
public string CurrentBuild { get; }
public ObservableCollection<string> Changes { get; } = new();
public bool RetrievingChanges
{
get => _retrievingChanges;
set => RaiseAndSetIfChanged(ref _retrievingChanges, value);
}
public bool HasChanges
{
get => _hasChanges;
set => RaiseAndSetIfChanged(ref _hasChanges, value);
}
public string? LatestBuild
{
get => _latestBuild;
set => RaiseAndSetIfChanged(ref _latestBuild, value);
}
private async Task GetBuildChanges()
{
try
{
RetrievingChanges = true;
Task<DevOpsBuild?> currentTask = _updateProvider.GetBuildInfo(1, CurrentBuild);
Task<DevOpsBuild?> latestTask = _updateProvider.GetBuildInfo(1);
DevOpsBuild? current = await currentTask;
DevOpsBuild? latest = await latestTask;
LatestBuild = latest?.BuildNumber;
if (current != null && latest != null)
{
GitHubDifference difference = await _updateProvider.GetBuildDifferences(current, latest);
// Only take commits with one parents (no merges)
Changes.Clear();
Changes.AddRange(difference.Commits.Where(c => c.Parents.Count == 1)
.SelectMany(c => c.Commit.Message.Split("\n"))
.Select(m => m.Trim())
.Where(m => !string.IsNullOrWhiteSpace(m) && !_excludedCommitMessages.Contains(m))
.OrderBy(m => m)
);
HasChanges = Changes.Any();
}
}
catch (Exception e)
{
_notificationService.CreateNotification().WithTitle("Failed to retrieve build changes").WithMessage(e.Message).WithSeverity(NotificationSeverity.Error).Show();
}
finally
{
RetrievingChanges = false;
}
}
}

View File

@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net6.0-windows7.0": { "net6.0-windows10.0.17763": {
"Avalonia": { "Avalonia": {
"type": "Direct", "type": "Direct",
"requested": "[0.10.15, )", "requested": "[0.10.15, )",
@ -65,6 +65,18 @@
"System.Numerics.Vectors": "4.5.0" "System.Numerics.Vectors": "4.5.0"
} }
}, },
"Microsoft.Toolkit.Uwp.Notifications": {
"type": "Direct",
"requested": "[7.1.2, )",
"resolved": "7.1.2",
"contentHash": "cVBRDG8g0K4sBRHFJoNQjwZduDUz+cXP33erIQjAa8fGKF4DsWIuOAQCYGFoXpdXIXjxoaUYuGSxAG6QCvOdtQ==",
"dependencies": {
"Microsoft.Win32.Registry": "4.7.0",
"System.Drawing.Common": "4.7.0",
"System.Reflection.Emit": "4.7.0",
"System.ValueTuple": "4.5.0"
}
},
"Microsoft.Win32": { "Microsoft.Win32": {
"type": "Direct", "type": "Direct",
"requested": "[2.0.1, )", "requested": "[2.0.1, )",
@ -461,12 +473,21 @@
"System.Runtime": "4.3.0" "System.Runtime": "4.3.0"
} }
}, },
"Microsoft.Win32.Registry": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==",
"dependencies": {
"System.Security.AccessControl": "4.7.0",
"System.Security.Principal.Windows": "4.7.0"
}
},
"Microsoft.Win32.SystemEvents": { "Microsoft.Win32.SystemEvents": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.5.0", "resolved": "4.7.0",
"contentHash": "LuI1oG+24TUj1ZRQQjM5Ew73BKnZE5NZ/7eAdh1o8ST5dPhUnJvIkiIn2re3MwnkRy6ELRnvEbBxHP8uALKhJw==", "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==",
"dependencies": { "dependencies": {
"Microsoft.NETCore.Platforms": "2.0.0" "Microsoft.NETCore.Platforms": "3.1.0"
} }
}, },
"NETStandard.Library": { "NETStandard.Library": {
@ -977,11 +998,11 @@
}, },
"System.Drawing.Common": { "System.Drawing.Common": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.5.0", "resolved": "4.7.0",
"contentHash": "AiJFxxVPdeITstiRS5aAu8+8Dpf5NawTMoapZ53Gfirml24p7HIfhjmCRxdXnmmf3IUA3AX3CcW7G73CjWxW/Q==", "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==",
"dependencies": { "dependencies": {
"Microsoft.NETCore.Platforms": "2.0.0", "Microsoft.NETCore.Platforms": "3.1.0",
"Microsoft.Win32.SystemEvents": "4.5.0" "Microsoft.Win32.SystemEvents": "4.7.0"
} }
}, },
"System.Dynamic.Runtime": { "System.Dynamic.Runtime": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

View File

@ -21,14 +21,15 @@ namespace Artemis.UI.Screens.Root
{ {
public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvider public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvider
{ {
private readonly IAssetLoader _assetLoader;
private readonly DefaultTitleBarViewModel _defaultTitleBarViewModel; private readonly DefaultTitleBarViewModel _defaultTitleBarViewModel;
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IDebugService _debugService;
private readonly IClassicDesktopStyleApplicationLifetime _lifeTime;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly ISidebarVmFactory _sidebarVmFactory;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private readonly IDebugService _debugService;
private readonly IUpdateService _updateService;
private readonly IAssetLoader _assetLoader;
private readonly IClassicDesktopStyleApplicationLifetime _lifeTime;
private readonly ISidebarVmFactory _sidebarVmFactory;
private SidebarViewModel? _sidebarViewModel; private SidebarViewModel? _sidebarViewModel;
private ViewModelBase? _titleBarViewModel; private ViewModelBase? _titleBarViewModel;
@ -38,6 +39,7 @@ namespace Artemis.UI.Screens.Root
IWindowService windowService, IWindowService windowService,
IMainWindowService mainWindowService, IMainWindowService mainWindowService,
IDebugService debugService, IDebugService debugService,
IUpdateService updateService,
IAssetLoader assetLoader, IAssetLoader assetLoader,
DefaultTitleBarViewModel defaultTitleBarViewModel, DefaultTitleBarViewModel defaultTitleBarViewModel,
ISidebarVmFactory sidebarVmFactory) ISidebarVmFactory sidebarVmFactory)
@ -48,6 +50,7 @@ namespace Artemis.UI.Screens.Root
_settingsService = settingsService; _settingsService = settingsService;
_windowService = windowService; _windowService = windowService;
_debugService = debugService; _debugService = debugService;
_updateService = updateService;
_assetLoader = assetLoader; _assetLoader = assetLoader;
_defaultTitleBarViewModel = defaultTitleBarViewModel; _defaultTitleBarViewModel = defaultTitleBarViewModel;
_sidebarVmFactory = sidebarVmFactory; _sidebarVmFactory = sidebarVmFactory;

View File

@ -6,6 +6,8 @@
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings" xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="2400" mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="2400"
x:Class="Artemis.UI.Screens.Settings.GeneralTabView" x:Class="Artemis.UI.Screens.Settings.GeneralTabView"
x:DataType="settings:GeneralTabViewModel"> x:DataType="settings:GeneralTabViewModel">
@ -18,40 +20,46 @@
</TextBlock> </TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel> <StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
<StackPanel Grid.Column="0"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<TextBlock>Auto-run on startup</TextBlock> <StackPanel Grid.Column="0">
</StackPanel> <TextBlock>Auto-run on startup</TextBlock>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/> </StackPanel>
</Grid> <ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/>
<Separator Classes="card-separator" /> </Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock>Hide window on auto-run</TextBlock> <TextBlock>Hide window on auto-run</TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/> <ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Separator Classes="card-separator" /> <Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
<TextBlock>Startup delay</TextBlock> <TextBlock>Startup delay</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
Set the amount of seconds to wait before auto-running Artemis. Set the amount of seconds to wait before auto-running Artemis.
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap"> <TextBlock Classes="subtitle" TextWrapping="Wrap">
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<TextBox Text="{CompiledBinding UIAutoRunDelay.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120" /> <controls:NumberBox IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120">
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock> <Interaction.Behaviors>
</StackPanel> <behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding UIAutoRunDelay.Value}"/>
</Grid> </Interaction.Behaviors>
<Separator Classes="card-separator" /> </controls:NumberBox>
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
</StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0"> <StackPanel Grid.Column="0">
@ -103,48 +111,69 @@
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<TextBox Text="{CompiledBinding WebServerPort.Value}" Width="150" /> <controls:NumberBox Width="150">
<Interaction.Behaviors>
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding WebServerPort.Value}"/>
</Interaction.Behaviors>
</controls:NumberBox>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Update settings --> <!-- Update settings -->
<TextBlock Classes="h4" Margin="0 15"> <StackPanel IsVisible="{CompiledBinding IsUpdatingSupported}">
Updating <TextBlock Classes="h4" Margin="0 15">
</TextBlock> Updating
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0"> </TextBlock>
<StackPanel> <Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <StackPanel>
<StackPanel Grid.Column="0"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<TextBlock> <StackPanel Grid.Column="0">
Check for updates <TextBlock>
</TextBlock> Check for updates
<TextBlock Classes="subtitle" TextWrapping="Wrap"> </TextBlock>
If enabled, we'll check for updates on startup and periodically while running. <TextBlock Classes="subtitle" TextWrapping="Wrap">
</TextBlock> If enabled, we'll check for updates on startup and periodically while running.
</StackPanel> </TextBlock>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> </StackPanel>
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" /> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
</StackPanel> <ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
</Grid> </StackPanel>
<Separator Classes="card-separator" /> </Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Auto-install updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, new updates will automatically be installed.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto"> <Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center"> <StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock> <TextBlock>
Update Update
</TextBlock> </TextBlock>
<TextBlock Classes="subtitle"> <TextBlock Classes="subtitle">
Use the button on the right to check for updates now. Use the button on the right to check for updates now.
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{Binding OfferUpdatesIfFound}" Width="150" Content="Check now" /> <Button Command="{CompiledBinding CheckForUpdate}" Width="150" Content="Check now" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel>
<!-- Profile editor settings --> <!-- Profile editor settings -->
<TextBlock Classes="h4" Margin="0 15"> <TextBlock Classes="h4" Margin="0 15">

View File

@ -9,14 +9,15 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.Providers;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard; using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Avalonia; using Avalonia.Threading;
using DynamicData; using DynamicData;
using FluentAvalonia.Styling;
using Ninject; using Ninject;
using ReactiveUI; using ReactiveUI;
using Serilog.Events; using Serilog.Events;
@ -29,20 +30,28 @@ namespace Artemis.UI.Screens.Settings
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IDebugService _debugService; private readonly IDebugService _debugService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private readonly IUpdateService _updateService;
private readonly IAutoRunProvider? _autoRunProvider;
public GeneralTabViewModel(IKernel kernel, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDebugService debugService, IWindowService windowService) public GeneralTabViewModel(IKernel kernel,
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IDebugService debugService,
IWindowService windowService,
IUpdateService updateService)
{ {
DisplayName = "General"; DisplayName = "General";
_settingsService = settingsService; _settingsService = settingsService;
_debugService = debugService; _debugService = debugService;
_windowService = windowService; _windowService = windowService;
_updateService = updateService;
_autoRunProvider = kernel.TryGet<IAutoRunProvider>();
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>(); List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
List<IGraphicsContextProvider> graphicsContextProviders = kernel.Get<List<IGraphicsContextProvider>>();
LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors)); LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
GraphicsContexts = new ObservableCollection<string> {"Software"}; GraphicsContexts = new ObservableCollection<string> {"Software"};
IGraphicsContextProvider? graphicsContextProvider = kernel.TryGet<IGraphicsContextProvider>(); GraphicsContexts.AddRange(graphicsContextProviders.Select(p => p.GraphicsContextName));
if (graphicsContextProvider != null)
GraphicsContexts.AddRange(graphicsContextProvider.GraphicsContextNames);
_defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference _defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
{ {
@ -55,6 +64,21 @@ namespace Artemis.UI.Screens.Settings
ShowSetupWizard = ReactiveCommand.CreateFromTask(ExecuteShowSetupWizard); ShowSetupWizard = ReactiveCommand.CreateFromTask(ExecuteShowSetupWizard);
ShowDebugger = ReactiveCommand.Create(ExecuteShowDebugger); ShowDebugger = ReactiveCommand.Create(ExecuteShowDebugger);
ShowDataFolder = ReactiveCommand.Create(ExecuteShowDataFolder); ShowDataFolder = ReactiveCommand.Create(ExecuteShowDataFolder);
this.WhenActivated(d =>
{
UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged;
Dispatcher.UIThread.InvokeAsync(ApplyAutoRun);
Disposable.Create(() =>
{
UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged;
_settingsService.SaveAllSettings();
}).DisposeWith(d);
});
} }
public ReactiveCommand<Unit, Unit> ShowLogs { get; } public ReactiveCommand<Unit, Unit> ShowLogs { get; }
@ -63,6 +87,9 @@ namespace Artemis.UI.Screens.Settings
public ReactiveCommand<Unit, Unit> ShowDebugger { get; } public ReactiveCommand<Unit, Unit> ShowDebugger { get; }
public ReactiveCommand<Unit, Unit> ShowDataFolder { get; } public ReactiveCommand<Unit, Unit> ShowDataFolder { get; }
public bool IsAutoRunSupported => _autoRunProvider != null;
public bool IsUpdatingSupported => _updateService.UpdatingSupported;
public ObservableCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; } public ObservableCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; }
public ObservableCollection<string> GraphicsContexts { get; } public ObservableCollection<string> GraphicsContexts { get; }
@ -116,6 +143,7 @@ namespace Artemis.UI.Screens.Settings
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15); public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true); public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true); public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.AutoUpdate", false);
public PluginSetting<bool> ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting<bool> ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<LogEventLevel> CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information); public PluginSetting<LogEventLevel> CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
@ -123,26 +151,17 @@ namespace Artemis.UI.Screens.Settings
public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30); public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30);
public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696); public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696);
#region General
private void ExecuteShowLogs() private void ExecuteShowLogs()
{ {
OpenFolder(Constants.LogsFolder); Utilities.OpenFolder(Constants.LogsFolder);
} }
#endregion
#region Updating private async Task ExecuteCheckForUpdate(CancellationToken cancellationToken)
private Task ExecuteCheckForUpdate(CancellationToken cancellationToken)
{ {
return Task.CompletedTask; await _updateService.ManualUpdate();
} }
#endregion
#region Tools
private async Task ExecuteShowSetupWizard() private async Task ExecuteShowSetupWizard()
{ {
await _windowService.ShowDialogAsync<StartupWizardViewModel, bool>(); await _windowService.ShowDialogAsync<StartupWizardViewModel, bool>();
@ -153,19 +172,46 @@ namespace Artemis.UI.Screens.Settings
_debugService.ShowDebugger(); _debugService.ShowDebugger();
} }
private void ExecuteShowDataFolder() private void ExecuteShowDataFolder()
{ {
OpenFolder(Constants.DataFolder); Utilities.OpenFolder(Constants.DataFolder);
} }
#endregion private async Task ApplyAutoRun()
private void OpenFolder(string path)
{ {
if (OperatingSystem.IsWindows()) if (_autoRunProvider == null)
return;
try
{ {
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", path); if (UIAutoRun.Value)
await _autoRunProvider.EnableAutoRun(false, UIAutoRunDelay.Value);
else
await _autoRunProvider.DisableAutoRun();
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
}
}
private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e)
{
await ApplyAutoRun();
}
private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e)
{
if (_autoRunProvider == null || !UIAutoRun.Value)
return;
try
{
await _autoRunProvider.EnableAutoRun(true, UIAutoRunDelay.Value);
}
catch (Exception exception)
{
_windowService.ShowExceptionDialog("Failed to apply auto-run", exception);
} }
} }
} }

View File

@ -0,0 +1,22 @@
using System.Threading.Tasks;
namespace Artemis.UI.Services.Interfaces;
public interface IUpdateService : IArtemisUIService
{
/// <summary>
/// Gets a boolean indicating whether updating is supported.
/// </summary>
bool UpdatingSupported { get; }
/// <summary>
/// Gets or sets a boolean indicating whether auto-updating is suspended.
/// </summary>
bool SuspendAutoUpdate { get; set; }
/// <summary>
/// Manually checks for updates and offers to install it if found.
/// </summary>
/// <returns>Whether an update was found, regardless of whether the user chose to install it.</returns>
Task ManualUpdate();
}

View File

@ -0,0 +1,113 @@
using System;
using System.Threading.Tasks;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services.MainWindow;
using Avalonia.Threading;
using Ninject;
using Serilog;
namespace Artemis.UI.Services;
public class UpdateService : IUpdateService
{
private const double UPDATE_CHECK_INTERVAL = 10000; // once per hour
private readonly PluginSetting<bool> _autoUpdate;
private readonly PluginSetting<bool> _checkForUpdates;
private readonly ILogger _logger;
private readonly IMainWindowService _mainWindowService;
private readonly IUpdateProvider? _updateProvider;
public UpdateService(ILogger logger, IKernel kernel, ISettingsService settingsService, IMainWindowService mainWindowService)
{
_logger = logger;
_mainWindowService = mainWindowService;
if (!Constants.BuildInfo.IsLocalBuild)
_updateProvider = kernel.TryGet<IUpdateProvider>();
_checkForUpdates = settingsService.GetSetting("UI.CheckForUpdates", true);
_autoUpdate = settingsService.GetSetting("UI.AutoUpdate", false);
_checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged;
_mainWindowService.MainWindowOpened += WindowServiceOnMainWindowOpened;
Timer timer = new(UPDATE_CHECK_INTERVAL);
timer.Elapsed += TimerOnElapsed;
timer.Start();
}
private async void TimerOnElapsed(object? sender, ElapsedEventArgs e)
{
await AutoUpdate();
}
private async void CheckForUpdatesOnSettingChanged(object? sender, EventArgs e)
{
// Run an auto-update as soon as the setting gets changed to enabled
if (_checkForUpdates.Value)
await AutoUpdate();
}
private async void WindowServiceOnMainWindowOpened(object? sender, EventArgs e)
{
// await AutoUpdate();
}
private async Task AutoUpdate()
{
if (_updateProvider == null || !_checkForUpdates.Value || SuspendAutoUpdate)
return;
try
{
bool updateAvailable = await _updateProvider.CheckForUpdate("master");
if (!updateAvailable)
return;
// Only offer it once per session
SuspendAutoUpdate = true;
// If the window is open show the changelog, don't auto-update while the user is busy
if (_mainWindowService.IsMainWindowOpen)
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
// Call OpenMainWindow anyway to focus the main window
_mainWindowService.OpenMainWindow();
await _updateProvider.OfferUpdate("master", true);
});
return;
}
// If the window is closed but auto-update is enabled, update silently
if (_autoUpdate.Value)
await _updateProvider.ApplyUpdate("master", true);
// If auto-update is disabled the update provider can show a notification and handle the rest
else
await _updateProvider.OfferUpdate("master", false);
}
catch (Exception e)
{
_logger.Warning(e, "Auto update failed");
}
}
public bool SuspendAutoUpdate { get; set; }
public bool UpdatingSupported => _updateProvider != null;
public async Task ManualUpdate()
{
if (_updateProvider == null || !_mainWindowService.IsMainWindowOpen)
return;
bool updateAvailable = await _updateProvider.CheckForUpdate("master");
if (!updateAvailable)
return;
await _updateProvider.OfferUpdate("master", true);
}
}