Compare commits

...
20 Commits
Author SHA1 Message Date
Alexandru Macocian 1d7adf3944 Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-04-10 19:00:46 +02:00
Alexandru Macocian fb7399adf8 Add support for experimental features. 2021-04-10 19:00:39 +02:00
amacocianandGitHub 6ebbffdda1 Update README.md 2021-04-10 11:25:13 +02:00
Alexandru Macocian 2c35333cac Handle missing dependency on webview2 browser. 2021-04-10 11:18:46 +02:00
Alexandru Macocian fe5e38b216 Change update process to modify and restore execution policy before and after update.
Change update process to wait for the client to close instead of a static wait.
2021-04-10 10:31:36 +02:00
Alexandru Macocian 9fd7d8b62a Ask for execution policy change before running script. 2021-04-09 17:19:34 +02:00
Alexandru Macocian a9aa73033f Update to 0.2 2021-04-09 15:56:11 +02:00
Alexandru Macocian 73dfca0bc0 Include updater in main application. 2021-04-09 15:52:33 +02:00
Alexandru Macocian cdc9b6b011 Overwrite files during extraction 2021-04-09 13:36:29 +02:00
Alexandru Macocian bbdd365a28 Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-04-09 13:33:36 +02:00
Alexandru Macocian ec19ce6561 Source code for updater utility 2021-04-09 13:33:31 +02:00
amacocianandGitHub 9e62860d8d Update README.md 2021-04-09 12:31:27 +02:00
Alexandru Macocian 4b6d6cf651 Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-04-09 12:20:05 +02:00
Alexandru Macocian 4af457710f Implement GWToolbox launcher. 2021-04-09 12:19:58 +02:00
amacocianandGitHub f4dc05d7d0 Update README.md 2021-04-08 16:08:44 +02:00
Alexandru Macocian eb5552bf71 Ability to maximize/resize browsers 2021-04-08 16:03:28 +02:00
Alexandru Macocian 1f3cd9d2df Daybreak 0.1.2 2021-04-08 14:04:14 +02:00
Alexandru Macocian e219d91b56 Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-04-08 14:03:50 +02:00
Alexandru Macocian ea21820c88 Setting to toggle address bar readonly property. 2021-04-08 14:03:44 +02:00
amacocianandGitHub 82da4e169c Update README.md 2021-04-08 00:36:18 +02:00
26 changed files with 1046 additions and 89 deletions
@@ -6,6 +6,8 @@ namespace Daybreak.Configuration
{
[JsonProperty("GamePath")]
public string GamePath { get; set; }
[JsonProperty("ToolboxPath")]
public string ToolboxPath { get; set; }
[JsonProperty("CharacterName")]
public string CharacterName { get; set; }
[JsonProperty("LeftBrowserDefault")]
@@ -16,5 +18,9 @@ namespace Daybreak.Configuration
public string ProtectedUsername { get; set; }
[JsonProperty("ProtectedPassword")]
public string ProtectedPassword { get; set; }
[JsonProperty("AddressBarReadonly")]
public bool AddressBarReadonly { get; set; } = true;
[JsonProperty("ExperimentalFeatures")]
public ExperimentalFeatures ExperimentalFeatures { get; set; } = new();
}
}
@@ -0,0 +1,6 @@
namespace Daybreak.Configuration
{
public sealed class ExperimentalFeatures
{
}
}
@@ -5,6 +5,7 @@ using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.Logging;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Microsoft.Web.WebView2.Core;
@@ -29,7 +30,7 @@ namespace Daybreak.Configuration
serviceProducer.RegisterSingleton<IScreenshotProvider, ScreenshotProvider>();
serviceProducer.RegisterSingleton<IConfigurationManager, ConfigurationManager>();
serviceProducer.RegisterSingleton<IBloogumClient, BloogumClient>();
serviceProducer.RegisterSingleton<CoreWebView2Environment, CoreWebView2Environment>((sp) => TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null)));
serviceProducer.RegisterSingleton<IApplicationUpdater, ApplicationUpdater>();
}
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
@@ -37,13 +38,16 @@ namespace Daybreak.Configuration
applicationLifetimeProducer.RegisterService<ILoggingDatabase>();
applicationLifetimeProducer.RegisterService<IScreenshotProvider>();
applicationLifetimeProducer.RegisterService<IApplicationUpdater>();
}
public static void RegisterViews(IViewProducer viewProducer)
{
viewProducer.ThrowIfNull(nameof(viewProducer));
viewProducer.RegisterView<StartupView>();
viewProducer.RegisterView<MainView>();
viewProducer.RegisterView<SettingsView>();
viewProducer.RegisterView<AskUpdateView>();
viewProducer.RegisterView<UpdateView>();
}
}
}
+29 -5
View File
@@ -19,8 +19,9 @@
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"></wv2:WebView2>
<Grid Grid.Row="1" Background="#80808080">
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></wv2:WebView2>
<Grid Grid.Row="1" Background="#80808080" IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
@@ -48,12 +49,35 @@
</WrapPanel>
<TextBox Text="{Binding ElementName=_this, Path=Address, Mode=OneWay}" FontSize="16"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Grid.Column="1" IsReadOnly="True" Background="Transparent"
Grid.Column="1" IsReadOnly="{Binding ElementName=_this, Path=AddressBarReadonly, Mode=OneWay}" Background="Transparent"
BorderThickness="1" VerticalAlignment="Center" VerticalContentAlignment="Center"
BorderBrush="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></TextBox>
<local:StarGlyph Grid.Column="2" Height="30" Width="30" Margin="5"
BorderBrush="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
PreviewKeyDown="TextBox_PreviewKeyDown"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></TextBox>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<local:StarGlyph Height="30" Width="30" Margin="5"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
Clicked="StarGlyph_Clicked" x:Name="FavoriteButton"></local:StarGlyph>
<local:MaximizeButton Height="30" Width="30" Margin="5"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
Clicked="MaximizeButton_Clicked"></local:MaximizeButton>
</StackPanel>
</Grid>
<Grid Grid.RowSpan="2" Background="Gray"
Visibility="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay,Converter={StaticResource BooleanToVisibilityConverter}}">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Foreground="White" Text="Browser not supported." FontSize="26" TextWrapping="Wrap"></TextBlock>
<TextBlock Foreground="White" Text="Download the Evergreen Bootstrapper from here:" FontSize="26" TextWrapping="Wrap" />
<TextBox Background="Transparent" BorderBrush="Transparent" BorderThickness="0"
Text="https://go.microsoft.com/fwlink/p/?LinkId=2124703"
IsReadOnly="True" Margin="0, 0, 0, 30" FontSize="22" Foreground="Blue"
PreviewMouseLeftButtonDown="Hyperlink_PreviewMouseLeftButtonDown" Cursor="Hand"
TextWrapping="Wrap"></TextBox>
<TextBlock Foreground="White" Text="Restart after the installation." FontSize="26" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Grid>
</UserControl>
@@ -1,7 +1,12 @@
using Daybreak.Launch;
using Daybreak.Services.Configuration;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Web.WebView2.Core;
using System;
using System.Diagnostics;
using System.Extensions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
@@ -13,36 +18,53 @@ namespace Daybreak.Controls
/// </summary>
public partial class ChromiumBrowserWrapper : UserControl
{
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
public readonly static DependencyProperty AddressProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(Address));
public readonly static DependencyProperty FavoriteAddressProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(FavoriteAddress));
public readonly static DependencyProperty NavigatingProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(Navigating));
public readonly static DependencyProperty AddressBarReadonlyProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(AddressBarReadonly));
public readonly static DependencyProperty BrowserSupportedProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(BrowserSupported), new PropertyMetadata(true));
public event EventHandler<string> FavoriteUriChanged;
public event EventHandler MaximizeClicked;
private readonly CoreWebView2Environment coreWebView2Environment;
private readonly IConfigurationManager configurationManager;
private readonly ILogger logger;
private CoreWebView2Environment coreWebView2Environment;
public string Address
{
get => this.GetTypedValue<string>(AddressProperty);
set => this.SetTypedValue(AddressProperty, value);
}
public string FavoriteAddress
{
get => this.GetTypedValue<string>(FavoriteAddressProperty);
set => this.SetTypedValue(FavoriteAddressProperty, value);
}
public bool Navigating
{
get => this.GetTypedValue<bool>(NavigatingProperty);
private set => this.SetTypedValue<bool>(NavigatingProperty, value);
}
public bool AddressBarReadonly
{
get => this.GetTypedValue<bool>(AddressBarReadonlyProperty);
private set => this.SetTypedValue<bool>(AddressBarReadonlyProperty, value);
}
public bool BrowserSupported
{
get => this.GetTypedValue<bool>(BrowserSupportedProperty);
private set => this.SetTypedValue<bool>(BrowserSupportedProperty, value);
}
public ChromiumBrowserWrapper()
{
this.coreWebView2Environment = Launcher.ApplicationServiceManager.GetService<CoreWebView2Environment>();
this.configurationManager = Launcher.ApplicationServiceManager.GetService<IConfigurationManager>();
this.logger = Launcher.ApplicationServiceManager.GetService<ILogger>();
this.InitializeComponent();
this.InitializeEnvironment();
this.InitializeBrowser();
}
@@ -55,12 +77,73 @@ namespace Daybreak.Controls
}
}
private async void InitializeBrowser()
public void ReinitializeBrowser()
{
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.NavigationStarting += (browser, args) => this.Navigating = true;
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
this.InitializeBrowser();
}
private void InitializeEnvironment()
{
try
{
this.coreWebView2Environment = System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null));
this.BrowserSupported = true;
}
catch(Exception e)
{
this.logger.LogWarning($"Browser initialization failed. Details: {e}");
this.BrowserSupported = false;
}
}
private async Task InitializeBrowser()
{
if (this.BrowserSupported is true)
{
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.NavigationStarting += (browser, args) => this.Navigating = true;
this.WebBrowser.NavigationCompleted += (browser, args) => this.Navigating = false;
}
}
private async void RetryInitializeButton_Clicked(object sender, EventArgs e)
{
this.InitializeEnvironment();
try
{
await this.InitializeBrowser();
}
catch
{
this.BrowserSupported = false;
}
}
private void Hyperlink_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (Uri.TryCreate(BrowserDownloadLink, UriKind.Absolute, out var uri))
{
Process.Start("explorer.exe", uri.ToString());
}
}
private void TextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter)
{
var newAddress = sender.As<TextBox>().Text;
newAddress = SanitizeAddress(newAddress);
if (newAddress is null)
{
return;
}
this.Address = newAddress;
this.WebBrowser.CoreWebView2.Navigate(this.Address);
e.Handled = true;
}
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
@@ -71,16 +154,19 @@ namespace Daybreak.Controls
private void BackButton_Clicked(object sender, EventArgs e)
{
this.WebBrowser.GoBack();
this.Address = this.WebBrowser.Source.ToString();
}
private void ForwardButton_Clicked(object sender, EventArgs e)
{
this.WebBrowser.GoForward();
this.Address = this.WebBrowser.Source.ToString();
}
private void RefreshGlyph_Clicked(object sender, EventArgs e)
{
this.WebBrowser.Reload();
this.Address = this.WebBrowser.Source.ToString();
}
private void CancelGlyph_Clicked(object sender, EventArgs e)
@@ -94,6 +180,11 @@ namespace Daybreak.Controls
this.FavoriteUriChanged?.Invoke(this, this.FavoriteAddress);
}
private void MaximizeButton_Clicked(object sender, EventArgs e)
{
this.MaximizeClicked?.Invoke(this, e);
}
private void CheckFavoriteAddress()
{
if (this.Address == this.FavoriteAddress)
@@ -105,5 +196,26 @@ namespace Daybreak.Controls
this.FavoriteButton.IsEnabled = true;
}
}
private static string SanitizeAddress(string address)
{
if (string.IsNullOrWhiteSpace(address))
{
return null;
}
if (address.StartsWith("www") is false &&
address.StartsWith("http") is false)
{
address = "https://www." + address;
}
else if (address.StartsWith("www"))
{
address = "https://" + address;
}
Uri.TryCreate(address, UriKind.Absolute, out var uri);
return uri?.ToString();
}
}
}
+39
View File
@@ -0,0 +1,39 @@
<UserControl x:Class="Daybreak.Controls.MaximizeButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="50">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.3*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.3*" />
<RowDefinition Height="1*" />
<RowDefinition Height="0.3*" />
</Grid.RowDefinitions>
<Ellipse x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Opacity="0.6" Visibility="Hidden" Grid.RowSpan="3" Grid.ColumnSpan="3"></Ellipse>
<Viewbox Grid.Row="1" Grid.Column="1">
<Grid>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m30,0l-24,0a5.9966,5.9966 0 0 0 -6,6l0,24a6,6 0 0 0 12,0l0,-18l18,0a6,6 0 0 0 0,-12z"></Path>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m90,0l-24,0a6,6 0 0 0 0,12l18,0l0,18a6,6 0 0 0 12,0l0,-24a5.9966,5.9966 0 0 0 -6,-6z"></Path>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m30,84l-18,0l0,-18a6,6 0 0 0 -12,0l0,24a5.9966,5.9966 0 0 0 6,6l24,0a6,6 0 0 0 0,-12z"></Path>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m90,60a5.9966,5.9966 0 0 0 -6,6l0,18l-18,0a6,6 0 0 0 0,12l24,0a5.9966,5.9966 0 0 0 6,-6l0,-24a5.9966,5.9966 0 0 0 -6,-6z"></Path>
</Grid>
</Viewbox>
<Ellipse Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" StrokeThickness="1" Fill="Transparent"
MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"
Grid.RowSpan="3" Grid.ColumnSpan="3"></Ellipse>
</Grid>
</UserControl>
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for SaveButton.xaml
/// </summary>
public partial class MaximizeButton : UserControl
{
public event EventHandler Clicked;
public MaximizeButton()
{
InitializeComponent();
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
BackgroundEllipse.Visibility = System.Windows.Visibility.Visible;
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
BackgroundEllipse.Visibility = System.Windows.Visibility.Hidden;
}
private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
Clicked?.Invoke(this, e);
}
}
}
}
+5 -1
View File
@@ -9,7 +9,7 @@
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<Version>0.1.1</Version>
<Version>0.3.1</Version>
</PropertyGroup>
<ItemGroup>
@@ -37,6 +37,10 @@
</ItemGroup>
<ItemGroup>
<Page Update="Controls\MaximizeButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Controls\StarGlyph.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
+19 -3
View File
@@ -1,5 +1,6 @@
using Daybreak.Services.Bloogum;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Pepa.Wpf.Utilities;
@@ -26,6 +27,7 @@ namespace Daybreak.Launch
private readonly IViewManager viewManager;
private readonly IScreenshotProvider screenshotProvider;
private readonly IBloogumClient bloogumClient;
private readonly IApplicationUpdater applicationUpdater;
private readonly CancellationTokenSource cancellationToken = new();
public string CreditText
@@ -37,18 +39,20 @@ namespace Daybreak.Launch
public MainWindow(
IViewManager viewManager,
IScreenshotProvider screenshotProvider,
IBloogumClient bloogumClient)
IBloogumClient bloogumClient,
IApplicationUpdater applicationUpdater)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenshotProvider = screenshotProvider.ThrowIfNull(nameof(screenshotProvider));
this.bloogumClient = bloogumClient.ThrowIfNull(nameof(bloogumClient));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.viewManager.ShowView<StartupView>();
this.SetupImageCycle();
this.CheckForUpdates();
}
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@@ -134,7 +138,6 @@ namespace Daybreak.Launch
{
var avgColor = GetAverageColor(bitmapImage);
var luminace = GetLuminace(avgColor);
Debug.WriteLine(luminace);
if (luminace < 0.15)
{
this.Foreground = Brushes.White;
@@ -146,6 +149,19 @@ namespace Daybreak.Launch
}
}
private async void CheckForUpdates()
{
var updateAvailable = await this.applicationUpdater.UpdateAvailable().ConfigureAwait(true);
if (updateAvailable)
{
this.viewManager.ShowView<AskUpdateView>();
}
else
{
this.viewManager.ShowView<MainView>();
}
}
private static Color GetAverageColor(BitmapSource bitmap)
{
var format = bitmap.Format;
+13
View File
@@ -0,0 +1,13 @@
namespace Daybreak.Models
{
public enum ExecutionPolicies
{
AllSigned,
Bypass,
Default,
RemoteSigned,
Restricted,
Undefined,
Unrestricted
}
}
+44
View File
@@ -0,0 +1,44 @@
using System.ComponentModel;
namespace Daybreak.Models
{
public sealed class UpdateStatus : INotifyPropertyChanged
{
public static readonly UpdateStep StartingStep = new("Starting");
public static readonly UpdateStep CheckingLatestVersion = new("Checking latest version");
public static UpdateStep Downloading(double progress) => new DownloadUpdateStep("Downloading", progress);
public static readonly UpdateStep DownloadFinished = new("Download finished. Application will restart in order to apply the update.");
private UpdateStep currentStep = StartingStep;
public event PropertyChangedEventHandler PropertyChanged;
public UpdateStep CurrentStep
{
get => this.currentStep;
set
{
this.currentStep = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
}
}
public class UpdateStep
{
public string Name { get; }
internal UpdateStep(string name)
{
this.Name = name;
}
}
public class DownloadUpdateStep : UpdateStep
{
internal DownloadUpdateStep(string name, double progress) : base(name)
{
this.Progress = progress;
}
public double Progress { get; }
}
}
}
@@ -11,12 +11,14 @@ namespace Daybreak.Services.ApplicationDetection
{
public class ApplicationDetector : IApplicationDetector
{
private const string ToolboxProcessName = "GWToolbox";
private const string ProcessName = "gw";
private readonly IConfigurationManager configurationManager;
private readonly ICredentialManager credentialManager;
public bool IsGuildwarsRunning => GuildwarsProcessDetected();
public bool IsToolboxRunning => GuildwarsToolboxProcessDetected();
public ApplicationDetector(
IConfigurationManager configurationManager,
@@ -46,7 +48,7 @@ namespace Daybreak.Services.ApplicationDetection
{
if (Process.Start(executable, new List<string> { "-email", credentials.Username, "-password", credentials.Password, "-character", configuration.CharacterName }) is null)
{
throw new InvalidOperationException($"Unable to launch executable");
throw new InvalidOperationException($"Unable to launch {executable}");
}
},
onNone: () =>
@@ -55,10 +57,29 @@ namespace Daybreak.Services.ApplicationDetection
});
}
public void LaunchGuildwarsToolbox()
{
var configuration = this.configurationManager.GetConfiguration();
var executable = configuration.ToolboxPath;
if (File.Exists(executable) is false)
{
throw new InvalidOperationException($"Guildwars executable doesn't exist at {executable}");
}
if (Process.Start(executable) is null)
{
throw new InvalidOperationException($"Unable to launch {executable}");
}
}
private static bool GuildwarsProcessDetected()
{
var process = Process.GetProcessesByName(ProcessName).FirstOrDefault();
return process is not null;
return Process.GetProcessesByName(ProcessName).FirstOrDefault() is not null;
}
private static bool GuildwarsToolboxProcessDetected()
{
return Process.GetProcessesByName(ToolboxProcessName).FirstOrDefault() is not null;
}
}
}
@@ -3,6 +3,8 @@
public interface IApplicationDetector
{
bool IsGuildwarsRunning { get; }
bool IsToolboxRunning { get; }
void LaunchGuildwars();
void LaunchGuildwarsToolbox();
}
}
@@ -1,7 +1,9 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using System;
using System.Extensions;
using System.IO;
namespace Daybreak.Services.Configuration
@@ -11,9 +13,11 @@ namespace Daybreak.Services.Configuration
private const string ConfigName = "Daybreak.config.json";
private ApplicationConfiguration applicationConfiguration;
private readonly ILogger logger;
public ConfigurationManager()
public ConfigurationManager(ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
try
{
var serializedConfig = File.ReadAllText(ConfigName);
@@ -21,7 +25,8 @@ namespace Daybreak.Services.Configuration
}
catch(Exception e)
{
throw new FatalException("Failed to load application configuration. See inner exception for details", e);
this.logger.LogWarning($"No configuration detected. Loading default configuration. Details: {e}");
this.applicationConfiguration = new ApplicationConfiguration();
}
}
@@ -0,0 +1,326 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
namespace Daybreak.Services.Updater
{
public sealed class ApplicationUpdater : IApplicationUpdater
{
private const string ExecutionPolicyKey = "ExecutionPolicy";
private const string UpdatedKey = "Updating";
private const string RegistryKey = "Daybreak";
private const string ExtractAndRunPs1 = "ExtractAndRun.ps1";
private const string TempFile = "tempfile.zip";
private const string VersionTag = "{VERSION}";
private const string InputFileTag = "{INPUTFILE}";
private const string OutputPathTag = "{OUTPUTPATH}";
private const string ExecutionPolicyTag = "{EXECUTIONPOLICY}";
private const string ProcessIdTag = "{PROCESSID}";
private const string Url = "https://github.com/AlexMacocian/Daybreak/releases/latest";
private const string DownloadUrl = $"https://github.com/AlexMacocian/Daybreak/releases/download/v{VersionTag}/Daybreakv{VersionTag}.zip";
private const string GetExecutionPolicyCommand = "Get-ExecutionPolicy -Scope CurrentUser";
private const string SetExecutionPolicyCommand = $"Set-ExecutionPolicy {ExecutionPolicyTag} -Scope CurrentUser";
private const string WaitCommand = $"Wait-Process -Id {ProcessIdTag}";
private const string ExtractCommandTemplate = $"Expand-Archive -Path '{InputFileTag}' -DestinationPath '{OutputPathTag}' -Force";
private const string RunClientCommand = @".\Daybreak.exe";
private const string RemoveTempFile = $"Remove-item {TempFile}";
private const string RemovePs1 = $"Remove-item {ExtractAndRunPs1}";
private readonly ILogger logger;
private readonly HttpClient httpClient = new();
public string CurrentVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
public ApplicationUpdater(ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
}
public async Task<bool> DownloadUpdate(UpdateStatus updateStatus)
{
updateStatus.CurrentStep = UpdateStatus.CheckingLatestVersion;
var latestVersion = (await this.GetLatestVersion()).ExtractValue();
if (latestVersion is null)
{
this.logger.LogWarning("Failed to retrieve latest version. Aborting update");
return false;
}
using var downloadLatestResponse = await this.httpClient.GetAsync(
DownloadUrl.Replace(VersionTag, latestVersion));
if (downloadLatestResponse.IsSuccessStatusCode is false)
{
this.logger.LogWarning("Failed to download latest version. Aborting udpate");
return false;
}
this.logger.LogInformation("Beginning update download");
var downloadStream = await downloadLatestResponse.Content.ReadAsStreamAsync();
var fileStream = File.OpenWrite(TempFile);
var downloadSize = (double)downloadStream.Length;
var buffer = new byte[1024];
var length = 0;
double downloaded = 0;
var tickTime = DateTime.Now;
while (downloadStream.CanRead && (length = await downloadStream.ReadAsync(buffer)) > 0)
{
downloaded += length;
await fileStream.WriteAsync(buffer, 0, length);
if ((DateTime.Now - tickTime).TotalMilliseconds > 50)
{
tickTime = DateTime.Now;
updateStatus.CurrentStep = UpdateStatus.Downloading(downloaded / downloadSize);
}
}
updateStatus.CurrentStep = UpdateStatus.Downloading(1);
updateStatus.CurrentStep = UpdateStatus.DownloadFinished;
fileStream.Close();
this.logger.LogInformation("Downloaded update");
return true;
}
public async Task<bool> UpdateAvailable()
{
var version = string.Join('.', this.CurrentVersion.Split('.').Take(3));
var maybeLatestVersion = await this.GetLatestVersion();
return maybeLatestVersion.Switch(
onSome: latestVersion => string.Compare(version, latestVersion, true) < 0,
onNone: () =>
{
this.logger.LogWarning("Failed to retrieve latest version");
return false;
}).ExtractValue();
}
public void FinalizeUpdate()
{
var maybeExecutionPolicy = this.RetrieveExecutionPolicy();
maybeExecutionPolicy.DoAny(
onNone: () =>
{
throw new InvalidOperationException("Failed to retrieve execution policy");
});
var executionPolicy = maybeExecutionPolicy.ExtractValue();
if (executionPolicy is not ExecutionPolicies.Bypass ||
executionPolicy is not ExecutionPolicies.Unrestricted)
{
this.logger.LogInformation($"Execution policy is set to {executionPolicy}. Setting to {ExecutionPolicies.Bypass}");
}
SaveExecutionPolicyValueToRegistry(executionPolicy);
MarkUpdateInRegistry();
this.SetExecutionPolicy(ExecutionPolicies.Bypass);
this.LaunchExtractor();
}
public void OnStartup()
{
if (UpdateMarkedInRegistry())
{
UnmarkUpdateInRegistry();
var maybeExecutionPolicy = LoadExecutionPolicyValueFromRegistry();
maybeExecutionPolicy.Do(
onSome: policy =>
{
SetExecutionPolicy(policy);
},
onNone: () =>
{
throw new InvalidOperationException("Found update marked in registry but no execution policy");
});
}
}
public void OnClosing()
{
}
private async Task<Optional<string>> GetLatestVersion()
{
using var response = await this.httpClient.GetAsync(Url);
if (response.IsSuccessStatusCode)
{
var versionTag = response.RequestMessage.RequestUri.ToString().Split('/').Last().TrimStart('v');
return versionTag;
}
return Optional.None<string>();
}
private Optional<ExecutionPolicies> RetrieveExecutionPolicy()
{
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = GetExecutionPolicyCommand,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
}
};
process.Start();
this.logger.LogInformation("Checking current execution policy");
var output = process.StandardOutput.ReadToEnd();
if (!Enum.TryParse(typeof(ExecutionPolicies), output, out var executionPolicy))
{
var error = process.StandardError.ReadToEnd();
this.logger.LogError($"Failed to retrieve current user execution policy. Stdout: {output}. Stderr: {error}");
return Optional.None<ExecutionPolicies>();
}
return executionPolicy.Cast<ExecutionPolicies>();
}
private void SetExecutionPolicy(ExecutionPolicies executionPolicy)
{
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell",
Arguments = SetExecutionPolicyCommand.Replace(ExecutionPolicyTag, executionPolicy.ToString()),
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
}
};
process.Start();
this.logger.LogInformation($"Setting execution policy to {executionPolicy}");
var output = process.StandardOutput.ReadToEnd();
if (!string.IsNullOrWhiteSpace(output))
{
var error = process.StandardError.ReadToEnd();
throw new InvalidOperationException($"Failed to set execution policy to {executionPolicy}. Stdout: {output}. Stderr: {error}");
}
}
private void LaunchExtractor()
{
File.WriteAllLines(ExtractAndRunPs1, new List<string>()
{
WaitCommand.Replace(ProcessIdTag, Environment.ProcessId.ToString()),
ExtractCommandTemplate
.Replace(InputFileTag, Path.GetFullPath(TempFile))
.Replace(OutputPathTag, Directory.GetCurrentDirectory()),
RemoveTempFile,
RemovePs1,
RunClientCommand
});
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $@"{Directory.GetCurrentDirectory()}\{ExtractAndRunPs1}",
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
WindowStyle = ProcessWindowStyle.Maximized,
WorkingDirectory = Directory.GetCurrentDirectory()
},
};
this.logger.LogInformation("Created extractor script. Attempting to launch powershell");
if (process.Start() is false)
{
throw new InvalidOperationException("Failed to create and start powershell script");
}
}
private static void MarkUpdateInRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
homeRegistryKey.SetValue(UpdatedKey, true);
homeRegistryKey.Close();
}
private static void UnmarkUpdateInRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
homeRegistryKey.SetValue(UpdatedKey, false);
homeRegistryKey.Close();
}
private static bool UpdateMarkedInRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
var update = homeRegistryKey.GetValue(UpdatedKey);
homeRegistryKey.Close();
if (update is string updateString)
{
if (bool.TryParse(updateString, out var updateValue))
{
return updateValue;
}
else
{
throw new InvalidOperationException($"Found update value {updateString} in registry");
}
}
return false;
}
private static void SaveExecutionPolicyValueToRegistry(ExecutionPolicies executionPolicy)
{
var homeRegistryKey = GetOrCreateHomeKey();
homeRegistryKey.SetValue(ExecutionPolicyKey, executionPolicy.ToString());
homeRegistryKey.Close();
}
private static Optional<ExecutionPolicies> LoadExecutionPolicyValueFromRegistry()
{
var homeRegistryKey = GetOrCreateHomeKey();
var executionPolicy = homeRegistryKey.GetValue(ExecutionPolicyKey);
homeRegistryKey.Close();
if (executionPolicy is null)
{
return Optional.None<ExecutionPolicies>();
}
else if (executionPolicy is string executionPolicyString)
{
if (Enum.TryParse<ExecutionPolicies>(executionPolicyString, out var executionPolicyValue))
{
return executionPolicyValue;
}
else
{
throw new InvalidOperationException($"Found execution policy with value {executionPolicy}");
}
}
else
{
throw new InvalidOperationException($"Found execution policy of type {executionPolicy.GetType()}.");
}
}
private static RegistryKey GetOrCreateHomeKey()
{
var homeRegistryKey = Registry.CurrentUser.OpenSubKey("Software", true).OpenSubKey(RegistryKey, true);
if (homeRegistryKey is null)
{
homeRegistryKey = Registry.CurrentUser.OpenSubKey("Software", true).CreateSubKey(RegistryKey, true);
}
return homeRegistryKey;
}
}
}
@@ -0,0 +1,14 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLifetime;
using System.Threading.Tasks;
namespace Daybreak.Services.Updater
{
public interface IApplicationUpdater : IApplicationLifetimeService
{
string CurrentVersion { get; }
void FinalizeUpdate();
Task<bool> UpdateAvailable();
Task<bool> DownloadUpdate(UpdateStatus updateStatus);
}
}
+21
View File
@@ -0,0 +1,21 @@
<UserControl x:Class="Daybreak.Views.AskUpdateView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
mc:Ignorable="d"
xmlns:controls="clr-namespace:Daybreak.Controls"
d:DesignHeight="450" d:DesignWidth="800">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Width="400" Height="200"
Background="White">
<TextBlock Text="An update has been detected. Do you wish to update?" FontSize="20" TextWrapping="Wrap"
Foreground="Black"></TextBlock>
<controls:OpaqueButton Text="No" Foreground="Black" TransparentBackground="Gray" BackgroundOpacity="0.2" Width="50" Height="30"
FontSize="16" HorizontalAlignment="Center" Margin="0, 0, 80, 0"
Clicked="NoButton_Clicked"></controls:OpaqueButton>
<controls:OpaqueButton Text="Yes" Foreground="Black" TransparentBackground="Gray" BackgroundOpacity="0.2" Width="50" Height="30"
FontSize="16" HorizontalAlignment="Center" Margin="80, 0, 0, 0"
Clicked="YesButton_Clicked"></controls:OpaqueButton>
</Grid>
</UserControl>
+42
View File
@@ -0,0 +1,42 @@
using Daybreak.Services.Logging;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using System.Extensions;
using System.Windows.Controls;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for AskUpdateView.xaml
/// </summary>
public partial class AskUpdateView : UserControl
{
private readonly IApplicationUpdater applicationUpdater;
private readonly ILogger logger;
private readonly IViewManager viewManager;
public AskUpdateView(
IApplicationUpdater applicationUpdater,
ILogger logger,
IViewManager viewManager)
{
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
}
private void NoButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User declined update");
this.viewManager.ShowView<MainView>();
}
private void YesButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User accepted update");
this.viewManager.ShowView<UpdateView>();
}
}
}
+48
View File
@@ -0,0 +1,48 @@
<UserControl x:Class="Daybreak.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
xmlns:controls="clr-namespace:Daybreak.Controls"
xmlns:effects="clr-namespace:System.Windows.Media.Extensions.Effects;assembly=WpfExtended"
mc:Ignorable="d"
x:Name="_this"
Loaded="StartupView_Loaded"
Unloaded="StartupView_Unloaded"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibility"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid Background="Transparent" x:Name="ViewContainer">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls:OpaqueButton Text="Launch game" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
Foreground="White" FontSize="36" Width="250" Height="60" VerticalAlignment="Bottom"
IsEnabled="{Binding ElementName=_this, Path=LaunchButtonEnabled, Mode=OneWay}"
Clicked="LaunchButton_Clicked" Grid.Row="0" Grid.ColumnSpan="3"></controls:OpaqueButton>
<controls:OpaqueButton Text="Launch toolbox" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
Foreground="White" FontSize="24" Width="200" Height="40" Margin="0, 10, 0, 10"
IsEnabled="{Binding ElementName=_this, Path=LaunchToolboxButtonEnabled, Mode=OneWay}"
Clicked="LaunchToolboxButton_Clicked" Grid.Row="1" Grid.ColumnSpan="3"></controls:OpaqueButton>
<Grid x:Name="RightContainer" Grid.Column="2" Margin="10">
<controls:ChromiumBrowserWrapper Address="{Binding ElementName=_this, Path=RightBrowserAddress, Mode=OneWay}"
Foreground="White" FavoriteUriChanged="RightBrowser_FavoriteUriChanged"
FavoriteAddress="{Binding ElementName=_this, Path=RightBrowserFavoriteAddress, Mode=OneWay}"
MaximizeClicked="RightChromiumBrowserWrapper_MaximizeClicked"/>
</Grid>
<Grid x:Name="LeftContainer" Grid.Column="0" Margin="10">
<controls:ChromiumBrowserWrapper Address="{Binding ElementName=_this, Path=LeftBrowserAddress, Mode=OneWay}"
Foreground="White" FavoriteUriChanged="LeftBrowser_FavoriteUriChanged"
FavoriteAddress="{Binding ElementName=_this, Path=LeftBrowserFavoriteAddress, Mode=OneWay}"
MaximizeClicked="LeftChromiumBrowserWrapper_MaximizeClicked"/>
</Grid>
</Grid>
</UserControl>
@@ -1,4 +1,5 @@
using Daybreak.Services.ApplicationDetection;
using Daybreak.Controls;
using Daybreak.Services.ApplicationDetection;
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using System;
@@ -13,24 +14,29 @@ namespace Daybreak.Views
/// <summary>
/// Interaction logic for StartupView.xaml
/// </summary>
public partial class StartupView : UserControl
public partial class MainView : UserControl
{
public static readonly DependencyProperty LaunchButtonEnabledProperty =
DependencyPropertyExtensions.Register<StartupView, bool>(nameof(LaunchButtonEnabled));
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchButtonEnabled));
public static readonly DependencyProperty LaunchToolboxButtonEnabledProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchToolboxButtonEnabled));
public static readonly DependencyProperty RightBrowserAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(RightBrowserAddress));
DependencyPropertyExtensions.Register<MainView, string>(nameof(RightBrowserAddress));
public static readonly DependencyProperty LeftBrowserAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(LeftBrowserAddress));
DependencyPropertyExtensions.Register<MainView, string>(nameof(LeftBrowserAddress));
public static readonly DependencyProperty RightBrowserFavoriteAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(RightBrowserFavoriteAddress));
DependencyPropertyExtensions.Register<MainView, string>(nameof(RightBrowserFavoriteAddress));
public static readonly DependencyProperty LeftBrowserFavoriteAddressProperty =
DependencyPropertyExtensions.Register<StartupView, string>(nameof(LeftBrowserFavoriteAddress));
DependencyPropertyExtensions.Register<MainView, string>(nameof(LeftBrowserFavoriteAddress));
private readonly IApplicationDetector applicationDetector;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly CancellationTokenSource cancellationTokenSource = new();
private bool leftBrowserMaximized = false;
private bool rightBrowserMaximized = false;
public string RightBrowserFavoriteAddress
{
get => this.GetTypedValue<string>(RightBrowserFavoriteAddressProperty);
@@ -56,8 +62,13 @@ namespace Daybreak.Views
get => this.GetTypedValue<bool>(LaunchButtonEnabledProperty);
set => this.SetTypedValue(LaunchButtonEnabledProperty, value);
}
public bool LaunchToolboxButtonEnabled
{
get => this.GetTypedValue<bool>(LaunchToolboxButtonEnabledProperty);
set => this.SetTypedValue(LaunchToolboxButtonEnabledProperty, value);
}
public StartupView(
public MainView(
IApplicationDetector applicationDetector,
IViewManager viewManager,
IConfigurationManager configurationManager)
@@ -86,14 +97,8 @@ namespace Daybreak.Views
private void CheckGameState()
{
if (applicationDetector.IsGuildwarsRunning)
{
this.LaunchButtonEnabled = false;
}
else
{
this.LaunchButtonEnabled = true;
}
this.LaunchButtonEnabled = applicationDetector.IsGuildwarsRunning is false;
this.LaunchToolboxButtonEnabled = applicationDetector.IsToolboxRunning is false;
}
private void StartupView_Loaded(object sender, RoutedEventArgs e)
@@ -105,7 +110,7 @@ namespace Daybreak.Views
this.cancellationTokenSource.Cancel();
}
private void OpaqueButton_Clicked(object sender, EventArgs e)
private void LaunchButton_Clicked(object sender, EventArgs e)
{
try
{
@@ -117,6 +122,18 @@ namespace Daybreak.Views
}
}
private void LaunchToolboxButton_Clicked(object sender, EventArgs e)
{
try
{
this.applicationDetector.LaunchGuildwarsToolbox();
}
catch
{
this.viewManager.ShowView<SettingsView>();
}
}
private void LeftBrowser_FavoriteUriChanged(object sender, string e)
{
var config = this.configurationManager.GetConfiguration();
@@ -130,5 +147,41 @@ namespace Daybreak.Views
config.RightBrowserDefault = e;
this.configurationManager.SaveConfiguration(config);
}
private void LeftChromiumBrowserWrapper_MaximizeClicked(object sender, EventArgs e)
{
if (this.leftBrowserMaximized)
{
this.ViewContainer.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[2].Width = new GridLength(1, GridUnitType.Star);
}
else
{
this.ViewContainer.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star);
}
this.leftBrowserMaximized = !this.leftBrowserMaximized;
}
private void RightChromiumBrowserWrapper_MaximizeClicked(object sender, EventArgs e)
{
if (this.rightBrowserMaximized)
{
this.ViewContainer.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[2].Width = new GridLength(1, GridUnitType.Star);
}
else
{
this.ViewContainer.ColumnDefinitions[0].Width = new GridLength(0, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Star);
this.ViewContainer.ColumnDefinitions[2].Width = new GridLength(1, GridUnitType.Star);
}
this.rightBrowserMaximized = !this.rightBrowserMaximized;
}
}
}
+12 -1
View File
@@ -15,6 +15,8 @@
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
@@ -38,6 +40,15 @@
x:Name="GamePathTextbox" FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"></TextBox>
<controls:FilePickerGlyph Grid.Row="4" Grid.Column="1" Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="1"
Clicked="FilePickerGlyph_Clicked"></controls:FilePickerGlyph>
Clicked="GameFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
<TextBlock Text="Toolbox path: " FontSize="22" Foreground="White" Grid.Row="5"></TextBlock>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="5"
x:Name="ToolboxPathTextbox" FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"></TextBox>
<controls:FilePickerGlyph Grid.Row="5" Grid.Column="1" Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="1"
Clicked="ToolboxFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
<TextBlock Text="Address bar readonly: " FontSize="22" Foreground="White" Grid.Row="6"/>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="6"
x:Name="AddressBarReadonlyTextbox" FontSize="22" Background="Transparent" Foreground="White"></TextBox>
</Grid>
</UserControl>
+27 -4
View File
@@ -39,9 +39,11 @@ namespace Daybreak.Views
this.UsernameTextbox.Text = credentials.Username;
this.PasswordBox.Password = credentials.Password;
});
});
this.AddressBarReadonlyTextbox.Text = config.AddressBarReadonly.ToString();
this.CharacterTextbox.Text = config.CharacterName;
this.GamePathTextbox.Text = config.GamePath;
this.ToolboxPathTextbox.Text = config.ToolboxPath;
}
private void SaveButton_Clicked(object sender, System.EventArgs e)
@@ -49,12 +51,18 @@ namespace Daybreak.Views
var currentConfig = this.configurationManager.GetConfiguration();
currentConfig.CharacterName = this.CharacterTextbox.Text;
currentConfig.GamePath = this.GamePathTextbox.Text;
currentConfig.ToolboxPath = this.ToolboxPathTextbox.Text;
if (bool.TryParse(this.AddressBarReadonlyTextbox.Text, out var addressBarReadonly))
{
currentConfig.AddressBarReadonly = addressBarReadonly;
}
this.configurationManager.SaveConfiguration(currentConfig);
this.credentialManager.StoreCredentials(new LoginCredentials { Username = this.UsernameTextbox.Text, Password = this.PasswordBox.Password });
this.viewManager.ShowView<StartupView>();
this.viewManager.ShowView<MainView>();
}
private void FilePickerGlyph_Clicked(object sender, System.EventArgs e)
private void GameFilePickerGlyph_Clicked(object sender, System.EventArgs e)
{
var filePicker = new OpenFileDialog()
{
@@ -69,9 +77,24 @@ namespace Daybreak.Views
}
}
private void ToolboxFilePickerGlyph_Clicked(object sender, System.EventArgs e)
{
var filePicker = new OpenFileDialog()
{
CheckFileExists = true,
CheckPathExists = true,
DefaultExt = "exe",
Multiselect = false
};
if (filePicker.ShowDialog() is true)
{
this.ToolboxPathTextbox.Text = filePicker.FileName;
}
}
private void BackButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<StartupView>();
this.viewManager.ShowView<MainView>();
}
}
}
-40
View File
@@ -1,40 +0,0 @@
<UserControl x:Class="Daybreak.Views.StartupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
xmlns:controls="clr-namespace:Daybreak.Controls"
xmlns:effects="clr-namespace:System.Windows.Media.Extensions.Effects;assembly=WpfExtended"
mc:Ignorable="d"
x:Name="_this"
Loaded="StartupView_Loaded"
Unloaded="StartupView_Unloaded"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibility"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls:OpaqueButton Text="Launch game" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
Foreground="White" FontSize="36" Width="250" Height="60" VerticalAlignment="Bottom" Margin="0, 0, 0, 30"
IsEnabled="{Binding ElementName=_this, Path=LaunchButtonEnabled, Mode=OneWay}"
Clicked="OpaqueButton_Clicked" Grid.Row="1" Grid.ColumnSpan="3"></controls:OpaqueButton>
<controls:ChromiumBrowserWrapper Grid.Column="2" Margin="10" Address="{Binding ElementName=_this, Path=RightBrowserAddress, Mode=OneWay}"
Foreground="White" FavoriteUriChanged="RightBrowser_FavoriteUriChanged"
FavoriteAddress="{Binding ElementName=_this, Path=RightBrowserFavoriteAddress, Mode=OneWay}"/>
<controls:ChromiumBrowserWrapper Grid.Column="0" Margin="10" Address="{Binding ElementName=_this, Path=LeftBrowserAddress, Mode=OneWay}"
Foreground="White" FavoriteUriChanged="LeftBrowser_FavoriteUriChanged"
FavoriteAddress="{Binding ElementName=_this, Path=LeftBrowserFavoriteAddress, Mode=OneWay}"/>
</Grid>
</UserControl>
+22
View File
@@ -0,0 +1,22 @@
<UserControl x:Class="Daybreak.Views.UpdateView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
mc:Ignorable="d"
x:Name="_this"
xmlns:controls="clr-namespace:Daybreak.Controls"
Loaded="UpdateView_Loaded"
d:DesignHeight="450" d:DesignWidth="800">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Background="White" MinHeight="200" MinWidth="400">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding ElementName=_this, Path=Description, Mode=OneWay}" Margin="10, 0, 10, 10" Foreground="Black"
TextWrapping="Wrap"></TextBlock>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding ElementName=_this, Path=ProgressValue, Mode=OneWay}" Width="300" Height="20"></ProgressBar>
<controls:OpaqueButton Text="Ok" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="30" Height="20"
IsEnabled="{Binding ElementName=_this, Path=ContinueButtonEnabled, Mode=OneWay}"
Clicked="OpaqueButton_Clicked" Foreground="Black"></controls:OpaqueButton>
</StackPanel>
</Grid>
</UserControl>
+102
View File
@@ -0,0 +1,102 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for UpdateView.xaml
/// </summary>
public partial class UpdateView : UserControl
{
public readonly static DependencyProperty DescriptionProperty =
DependencyPropertyExtensions.Register<UpdateView, string>(nameof(Description));
public readonly static DependencyProperty ProgressValueProperty =
DependencyPropertyExtensions.Register<UpdateView, double>(nameof(ProgressValue));
public readonly static DependencyProperty ContinueButtonEnabledProperty =
DependencyPropertyExtensions.Register<UpdateView, bool>(nameof(ContinueButtonEnabled));
private readonly ILogger logger;
private readonly IViewManager viewManager;
private readonly IApplicationUpdater applicationUpdater;
private readonly UpdateStatus updateStatus = new();
private bool success = false;
public string Description
{
get => this.GetTypedValue<string>(DescriptionProperty);
set => this.SetValue(DescriptionProperty, value);
}
public double ProgressValue
{
get => this.GetTypedValue<double>(ProgressValueProperty);
set => this.SetValue(ProgressValueProperty, value);
}
public bool ContinueButtonEnabled
{
get => this.GetTypedValue<bool>(ContinueButtonEnabledProperty);
set => this.SetValue(ContinueButtonEnabledProperty, value);
}
public UpdateView(
IApplicationUpdater applicationUpdater,
ILogger logger,
IViewManager viewManager)
{
this.applicationUpdater = applicationUpdater;
this.logger = logger;
this.viewManager = viewManager;
this.updateStatus.PropertyChanged += UpdateStatus_PropertyChanged;
this.InitializeComponent();
}
private void UpdateStatus_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
if (this.updateStatus.CurrentStep is UpdateStatus.DownloadUpdateStep downloadUpdateStep)
{
this.ProgressValue = downloadUpdateStep.Progress * 100;
}
this.Description = this.updateStatus.CurrentStep.Name;
});
}
private async void UpdateView_Loaded(object sender, RoutedEventArgs e)
{
this.logger.LogInformation("Starting update procedure");
var success = await applicationUpdater.DownloadUpdate(updateStatus).ConfigureAwait(true);
if (success is false)
{
this.logger.LogError("Update procedure failed");
}
else
{
this.success= true;
this.logger.LogInformation("Downloaded update");
}
this.ContinueButtonEnabled = true;
}
private void OpaqueButton_Clicked(object sender, System.EventArgs e)
{
if (this.success)
{
this.applicationUpdater.FinalizeUpdate();
Application.Current.Shutdown();
}
else
{
this.viewManager.ShowView<MainView>();
}
}
}
}
+4 -2
View File
@@ -1,13 +1,15 @@
# Daybreak
Custom client for Guildwars.
Requires standalone version https://developer.microsoft.com/microsoft-edge/webview2.
Requires webview2 runtime https://go.microsoft.com/fwlink/p/?LinkId=2124703.
![Alt Text](https://media1.giphy.com/media/Z32o0OZ5pZHDOIodzD/giphy.gif)
![Alt Text](https://media0.giphy.com/media/aQ8Wl7lsuhT0AblCPI/giphy.gif)
![Alt Text](https://media2.giphy.com/media/s06PtxgeAAZtoJhTx6/giphy.gif)
## Features
Automatically detect if guildwars is running or not. Includes the ability to launch guildwars from the client.
Manages username and password combination. Stores them in Windows Credential Manager.
Manages username and password combination.
Ability to set a character name which gets autoloaded during launch.