Compare commits

...
38 Commits
Author SHA1 Message Date
amacocianandGitHub 3798daa54e Update dependencies (#21)
* Update dependencies
* Minor nit fixes
* Fix UTs
2021-07-10 12:08:43 +03:00
amacocianandGitHub 624380ed86 Use IOptions instead of IConfigurationManager (#20)
* Use IOptions instead of dependency on IConfigurationManager
2021-06-22 15:48:37 +03:00
amacocianandGitHub 2b025e225b Updated wpfextended dependency 2021-06-11 13:50:58 +02:00
amacocianandGitHub 15a67ea28c Minor fixes
Stop showing browser when browser is disabled
Prevent setting address of browser when browser is disabled (should prevent browser from instantiating when disabled)
Ignore errors from browser caused by switching view before the browser could be initialized
Provide expression to filter logs
Button to export logs to json file
2021-06-07 14:33:54 +02:00
amacocianandGitHub c6b62fd5fd Logging improvements
Log uncaught exceptions
Logview expander
2021-06-07 00:34:43 +02:00
amacocianandGitHub 3c0877c1a0 Ability to keep a local cache of icons (#16) 2021-06-03 21:12:31 +02:00
967b57b9a8 Fix build code encoding/decoding view (#15)
Co-authored-by: Alexandru Macocian <almacoci@microsoft.com>
2021-06-03 20:40:56 +02:00
amacocianandGitHub f47cc319b5 Build template code field 2021-06-03 19:57:29 +02:00
amacocianandGitHub 26da84c4e7 Update to new version of wpfextended
Fix a bug where version parsing would ignore all 0s before the first digit
Remove LoggerFactory and LoggerProviders duplicated in WpfExtended
2021-06-03 14:48:35 +02:00
a39e8dc5c6 Separate CV from message (#12)
Co-authored-by: Alexandru Macocian <almacoci@microsoft.com>
2021-06-01 21:57:27 +02:00
1bb4669135 Introduce correlated logs (#11)
Co-authored-by: Alexandru Macocian <almacoci@microsoft.com>
2021-06-01 20:43:55 +02:00
amacocianandGitHub 0203eef006 Rework logging (#10)
* Rework log storing into a litedb.
LogView implementation to view logs.
Create crash.log on crash.

* Rename Logs namespace to Logging.
2021-05-18 19:49:51 +02:00
amacocianandGitHub 4ab50ee191 Use Microsoft.Logging.Abstractions
Typed ILogger.
2021-05-14 14:31:33 +02:00
amacocianandGitHub 06c1ec1ac2 Add scoped http client. (#8)
* Introduce scoped http client.
* Uts for scoped httpclient
2021-05-10 12:46:01 +02:00
amacocianandGitHub ef4befdc38 Version management view.
Enable/disable version autocheck.
Version management view.
2021-05-06 18:23:07 +02:00
amacocianandGitHub 33f49ddffa Cleanup cd pipeline (#6)
* Cleanup cd pipeline

* Remove cd pipeline on PR

* Cleanup CI pipeline comments.
2021-05-06 12:33:58 +02:00
amacocianandGitHub 49aa249b5b Generate changelog from last release tag.
Generate changelog from last release tag.
2021-05-05 23:01:57 +02:00
amacocianandGitHub c387d44890 Generate changelog from latest release.
Auto-generate changelog.
2021-05-05 21:31:45 +02:00
d4d09e7267 Update version (#3)
Co-authored-by: Alexandru Macocian <almacoci@microsoft.com>
2021-05-05 20:52:43 +02:00
amacocianandGitHub dd7bf02a97 Add CI & CD pipelines
Add CD&CI pipelines
2021-05-05 20:46:36 +02:00
Alexandru Macocian 6d0eeb478f Use source generator to generate dependency properties. 2021-05-05 17:23:37 +02:00
d4e9de48b1 Add ci pipeline (#1)
* Add ci pipeline

* Remove unused env variables.

* Change dotnet version to 5.0.5

* Change to dotnet 5.0.202

Co-authored-by: Alexandru Macocian <almacoci@microsoft.com>
2021-05-03 17:11:59 +02:00
Alexandru Macocian 6bd9877925 Remove dependency on InputSimulator. 2021-05-03 16:23:30 +02:00
Alexandru Macocian d38df6596f Remove keyboard macro functionality for now.
Unify input prompts.
Modify screenshot manager functionality to apply functionality of settings changed.
Update to 0.8.6.
2021-05-03 16:16:00 +02:00
Alexandru Macocian b4e31e859c Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-05-02 18:49:31 +02:00
Alexandru Macocian 1acd07c1be Change the way shortcut manager manages shortcuts.
Base for keyboard macros.
2021-05-02 18:49:25 +02:00
amacocianandGitHub b9b25bb098 Update README.md 2021-04-27 17:28:25 +02:00
amacocianandGitHub 5ea2981dc7 Update README.md 2021-04-27 17:25:03 +02:00
Alexandru Macocian 4fb96ca3a4 Ability to place screenshots in desired location. 2021-04-27 17:06:28 +02:00
Alexandru Macocian 6de52f5585 Ability to test placement while guildwars is running. 2021-04-26 11:26:43 +02:00
Alexandru Macocian 6622e41f64 Wait for guildwars window to be initialized before moving to desired window. 2021-04-26 11:02:23 +02:00
Alexandru Macocian 8405e4d1e5 Return to settings category after saving desired window. 2021-04-25 18:26:29 +02:00
Alexandru Macocian 0edc3f1560 Throw exception when screen id is invalid. 2021-04-25 18:24:51 +02:00
Alexandru Macocian c109fe7561 Ability to select on which window to place guildwars. 2021-04-25 18:20:45 +02:00
Alexandru Macocian b5413dca5c Include [Admin] tag when running as administrator. 2021-04-24 01:35:46 +02:00
Alexandru Macocian 6c2d4a1e70 No longer require administrator rights for normal usage.
Request elevation only in necesarry parts of the code.
Request elevation on update.
After update, launch application without elevation.
2021-04-24 01:20:36 +02:00
Alexandru Macocian be03f1a32d Run updates as admin 2021-04-23 23:37:01 +02:00
Alexandru Macocian 3ed1cc453a Implemented functionality to launch guildwars with lower privilege. 2021-04-23 20:24:28 +02:00
115 changed files with 3400 additions and 1249 deletions
+96
View File
@@ -0,0 +1,96 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# This continuous integration pipeline is triggered anytime a user pushes code to the repo.
# This pipeline builds the Wpf project, runs unit tests, then saves the MSIX build artifact.
name: Daybreak CD Pipeline
on:
push:
branches:
- master
jobs:
build:
strategy:
matrix:
targetplatform: [x64]
runs-on: windows-latest
env:
Configuration: Release
Solution_Path: Daybreak.sln
Test_Project_Path: Daybreak.Tests\Daybreak.Tests.csproj
Wpf_Project_Path: Daybreak\Daybreal.csproj
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get Latest Tag
id: getLatestTag
uses: WyriHaximus/github-action-get-previous-tag@v1
- name: Generate changelog
id: gen_changelog
run: |
$changeLog = git log --no-merges --pretty="%h - %s (%an)<br />" ${{ env.LatestReleaseTag }}..HEAD
echo "::set-env name=Changelog::$changeLog"
env:
LatestReleaseTag: ${{steps.getLatestTag.outputs.tag}}
- name: Print changelog
run: |
echo "${{ env.Changelog }}"
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.202'
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.1
- name: Restore project
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Build Daybreak project
run: dotnet build Daybreak -c $env:Configuration
- name: Set version variable
run: |
$version = .\Scripts\GetBuildVersion.ps1
echo "::set-env name=Version::$version"
- name: Create publish files
run: dotnet publish .\Daybreak\Daybreak.csproj -c $env:Configuration -r $env:RuntimeIdentifier -p:PublishReadyToRun=true -p:PublishSingleFile=true --self-contained true -o .\Publish
env:
RuntimeIdentifier: win-${{ matrix.targetplatform }}
- name: Pack publish files
run: |
Write-Host $env
.\Scripts\BuildRelease.ps1 -version $env:Version
shell: pwsh
- name: Publish release
uses: Xotl/cool-github-releases@v1.1.2
with:
mode: update
tag_name: v${{ env.Version }}
release_name: Daybreak v${{ env.Version }}
assets: .\Publish\daybreakv${{ env.Version }}.zip
github_token: ${{ env.GITHUB_TOKEN }}
replace_assets: true
body_mrkdwn: ${{ env.Changelog }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+53
View File
@@ -0,0 +1,53 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
# This continuous integration pipeline is triggered anytime a user pushes code to the repo.
# This pipeline builds the Wpf project, runs unit tests, then saves the MSIX build artifact.
name: Daybreak CI Pipeline
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
strategy:
matrix:
targetplatform: [x64]
runs-on: windows-latest
env:
Solution_Path: Daybreak.sln
Test_Project_Path: Daybreak.Tests\Daybreak.Tests.csproj
Wpf_Project_Path: Daybreak\Daybreal.csproj
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.202'
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.1
- name: Execute Unit Tests
run: dotnet test $env:Test_Project_Path
- name: Restore the Wpf application to populate the obj folder
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
env:
Configuration: Debug
RuntimeIdentifier: win-${{ matrix.targetplatform }}
+1
View File
@@ -9,6 +9,7 @@
*.user
*.userosscache
*.sln.docstates
*.version
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
+1
View File
@@ -13,6 +13,7 @@
<PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
+136
View File
@@ -0,0 +1,136 @@
using Daybreak.Models.Versioning;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
using Version = Daybreak.Models.Versioning.Version;
namespace Daybreak.Tests.Models
{
[TestClass]
public class VersionTests
{
[DataRow("v0.1.0.0.1", "v0.1.0.0.1")]
[DataRow("v0.1.0.0.1.0.0.0.0", "v0.1.0.0.1")]
[DataRow("v0.1.0", "v0.1")]
[DataRow("0.1.0", "0.1")]
[DataRow("v0.1.0.0.0", "v0.1")]
[DataRow("0.1.0.0.0", "0.1")]
[DataRow("v0", "v0")]
[DataRow("0", "0")]
[DataRow("v0.1", "v0.1")]
[DataRow("0.1-release", "0.1-release")]
[DataRow("v0.1-release", "v0.1-release")]
[TestMethod]
public void VersionToString(string version, string expectedString)
{
var parsedVersion = Version.Parse(version);
parsedVersion.ToString().Should().Be(expectedString);
}
[DataRow("v0", false)]
[DataRow("0", false)]
[DataRow("v0.1", false)]
[DataRow("0.1-release", true)]
[DataRow("v0.1-release", true)]
[TestMethod]
public void VersionHasStringSuffix(string version, bool hasSuffix)
{
var parsedVersion = Version.Parse(version);
if (hasSuffix)
{
parsedVersion.VersionTokens.Last().Should().BeOfType<VersionStringToken>();
}
else
{
parsedVersion.VersionTokens.Last().Should().BeOfType<VersionNumberToken>();
}
}
[DataRow("v0", true)]
[DataRow("0", false)]
[DataRow("v0.1", true)]
[DataRow("0.1", false)]
[TestMethod]
public void VersionTokensHasPrefix(string version, bool hasPrefix)
{
var parsedVersion = Version.Parse(version);
parsedVersion.HasPrefix.Should().Be(hasPrefix);
}
[DataRow(null, false)]
[DataRow("", false)]
[DataRow("asd", false)]
[DataRow("ads.pls.er", false)]
[DataRow("0.0.1", true)]
[DataRow("1", true)]
[DataRow("0.1.asd", false)]
[DataRow("0.", false)]
[DataRow(".1", false)]
[DataRow("0.1-", false)]
[DataRow("0.1-release", true)]
[DataRow("0..1", false)]
[TestMethod]
public void TryParseVersionTest(string version, bool result)
{
var success = Version.TryParse(version, out var parsedVersion);
success.Should().Be(result);
if (success is true)
{
parsedVersion.Should().NotBeNull();
}
}
[DataRow(null, false)]
[DataRow("", false)]
[DataRow("asd", false)]
[DataRow("ads.pls.er", false)]
[DataRow("0.0.1", true)]
[DataRow("1", true)]
[DataRow("0.1.asd", false)]
[DataRow("0.", false)]
[DataRow(".1", false)]
[DataRow("0.1-", false)]
[DataRow("0.1-release", true)]
[DataRow("0..1", false)]
[TestMethod]
public void ParseVersionTest(string version, bool shouldNotThrow)
{
var action = new Action(() => Version.Parse(version));
if (shouldNotThrow is false)
{
action.Should().Throw<InvalidOperationException>();
}
else
{
action.Should().NotThrow<InvalidOperationException>();
}
}
[DataRow(null, false)]
[DataRow("", false)]
[DataRow("asd", false)]
[DataRow("ads.pls.er", false)]
[DataRow("0.0.1", true)]
[DataRow("1", true)]
[DataRow("0.1.asd", false)]
[DataRow("0.", false)]
[DataRow(".1", false)]
[DataRow("0.1-", false)]
[DataRow("0.1-release", true)]
[DataRow("0..1", false)]
[TestMethod]
public void VersionConstructorTest(string version, bool shouldNotThrow)
{
var action = new Action(() => new Version(version));
if (shouldNotThrow is false)
{
action.Should().Throw<ArgumentException>();
}
else
{
action.Should().NotThrow<ArgumentException>();
}
}
}
}
@@ -0,0 +1,63 @@
using Daybreak.Configuration;
using Daybreak.Services.Configuration;
using Daybreak.Services.Options;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
namespace Daybreak.Tests.Services
{
[TestClass]
public class ApplicationConfigurationOptionsManagerTests
{
private ApplicationConfigurationOptionsManager applicationConfigurationOptionsManager;
[TestInitialize]
public void TestInitialize()
{
var configurationManagerMock = new Mock<IConfigurationManager>();
configurationManagerMock
.Setup(u => u.GetConfiguration())
.Returns(new ApplicationConfiguration());
this.applicationConfigurationOptionsManager = new ApplicationConfigurationOptionsManager(configurationManagerMock.Object);
}
[TestMethod]
public void GetApplicationConfiguration_ReturnsObject()
{
var config = this.applicationConfigurationOptionsManager.GetOptions<ApplicationConfiguration>();
config.Should().NotBeNull();
}
[TestMethod]
public void GetOtherOptions_ThrowsInvalidOperationException()
{
var action = new Action(() =>
{
this.applicationConfigurationOptionsManager.GetOptions<object>();
});
action.Should().Throw<InvalidOperationException>();
}
[TestMethod]
public void UpdateOptions_OnApplicationConfiguration_Succeeds()
{
this.applicationConfigurationOptionsManager.UpdateOptions(new ApplicationConfiguration());
}
[TestMethod]
public void UpdateOptions_OnOthers_ThrowsInvalidOperationException()
{
var action = new Action(() =>
{
this.applicationConfigurationOptionsManager.UpdateOptions(new object());
});
action.Should().Throw<InvalidOperationException>();
}
}
}
@@ -1,7 +1,7 @@
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Logging;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Collections.Generic;
@@ -17,7 +17,7 @@ namespace Daybreak.Tests.Services
[TestInitialize]
public void Initialize()
{
buildTemplateManager = new BuildTemplateManager(new Mock<ILogger>().Object);
buildTemplateManager = new BuildTemplateManager(new Mock<ILogger<BuildTemplateManager>>().Object);
}
[TestMethod]
@@ -0,0 +1,74 @@
using Daybreak.Services.Logging;
using FluentAssertions;
using LiteDB;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using System.Linq;
using System.Logging;
namespace Daybreak.Tests.Services
{
[TestClass]
public class JsonLoggerProviderTests
{
private ILogsManager logsManager;
private ILoggerProvider loggerProvider;
private ILiteDatabase liteDatabase;
[TestInitialize]
public void InitializeProvider()
{
File.Delete("Daybreak.db");
this.liteDatabase = new LiteDatabase("Daybreak.db");
this.logsManager = new JsonLogsManager(this.liteDatabase);
this.loggerProvider = new CVLoggerProvider(this.logsManager);
}
[TestCleanup]
public void TestCleanup()
{
this.liteDatabase.Dispose();
}
[TestMethod]
public void CreateLoggerReturnsLogger()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.Should().NotBeNull();
}
[TestMethod]
public void LoggerLogsAndReaderReadsFiltered()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.LogTrace("Logging some trace");
logger.LogInformation("Logging some stuff");
logger.LogError("Logging some error");
this.logsManager.GetLogs(l => l.LogLevel < LogLevel.Information).Should().HaveCount(1);
var log = this.logsManager.GetLogs(l => l.LogLevel < LogLevel.Information).First();
log.LogLevel.Should().Be(LogLevel.Error);
}
[TestMethod]
public void LoggerLogsAndReaderReads()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.LogInformation("Logging some stuff");
logger.LogError("Logging some error");
this.logsManager.GetLogs().Should().HaveCount(2);
}
[TestMethod]
public void DeletingLogsShouldDeleteLogs()
{
var logger = this.loggerProvider.CreateLogger("SomeCategory");
logger.LogInformation("Logging some stuff");
logger.LogError("Logging some error");
this.logsManager.GetLogs().Should().HaveCount(2);
this.logsManager.DeleteLogs();
this.logsManager.GetLogs().Should().HaveCount(0);
}
}
}
+5 -5
View File
@@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.31005.135
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak", "Daybreak\Daybreak.csproj", "{AA45C2B1-8BD0-466C-9271-699F168905AF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Daybreak.Tests", "Daybreak.Tests\Daybreak.Tests.csproj", "{C911C1C2-6566-419C-89C7-F802EB3FE709}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.Tests", "Daybreak.Tests\Daybreak.Tests.csproj", "{C911C1C2-6566-419C-89C7-F802EB3FE709}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -17,12 +17,12 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Debug|x64.ActiveCfg = Debug|x64
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Debug|x64.Build.0 = Debug|x64
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Debug|x64.ActiveCfg = Debug|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Debug|x64.Build.0 = Debug|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|Any CPU.Build.0 = Release|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|x64.ActiveCfg = Release|x64
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|x64.Build.0 = Release|x64
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|x64.ActiveCfg = Release|Any CPU
{AA45C2B1-8BD0-466C-9271-699F168905AF}.Release|x64.Build.0 = Release|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -1,11 +1,16 @@
using Daybreak.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace Daybreak.Configuration
{
public sealed class ApplicationConfiguration
{
[JsonProperty("SetGuildwarsWindowSizeOnLaunch")]
public bool SetGuildwarsWindowSizeOnLaunch { get; set; }
[JsonProperty("DesiredGuildwarsScreen")]
public int DesiredGuildwarsScreen { get; set; }
[JsonProperty("BrowsersEnabled")]
public bool BrowsersEnabled { get; set; } = true;
[JsonProperty("ToolboxPath")]
@@ -26,5 +31,13 @@ namespace Daybreak.Configuration
public bool AddressBarReadonly { get; set; } = true;
[JsonProperty("ExperimentalFeatures")]
public ExperimentalFeatures ExperimentalFeatures { get; set; } = new();
[JsonProperty("ShortcutLocation")]
public string ShortcutLocation { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
[JsonProperty("PlaceShortcut")]
public bool PlaceShortcut { get; set; }
[JsonProperty("AutoCheckUpdate")]
public bool AutoCheckUpdate { get; set; } = true;
[JsonProperty("KeepLocalIconCache")]
public bool KeepLocalIconCache { get; set; } = true;
}
}
@@ -1,4 +1,6 @@
using Newtonsoft.Json;
using Daybreak.Models;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Daybreak.Configuration
{
@@ -10,5 +12,11 @@ namespace Daybreak.Configuration
public int ToolboxAutoLaunchDelay { get; set; } = 5000;
[JsonProperty("DynamicBuildLoading")]
public bool DynamicBuildLoading { get; set; } = true;
[JsonProperty("LaunchGuildwarsAsCurrentUser")]
public bool LaunchGuildwarsAsCurrentUser { get; set; } = true;
[JsonProperty("CanInterceptKeys")]
public bool CanInterceptKeys { get; set; }
[JsonProperty("Macros")]
public List<KeyMacro> Macros { get; set; } = new();
}
}
+41 -20
View File
@@ -1,5 +1,4 @@
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Services.Bloogum;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
@@ -7,45 +6,63 @@ using Daybreak.Services.Credentials;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Runtime;
using Daybreak.Services.Privilege;
using Daybreak.Services.Screens;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Shortcuts;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Microsoft.Extensions.Http.Logging;
using Microsoft.Extensions.Logging;
using Slim;
using System.Extensions;
using System.Net.Http;
using LiteDB;
using Daybreak.Services.Options;
using System.Http;
namespace Daybreak.Configuration
{
public static class ProjectConfiguration
{
public static void RegisterResolvers(IServiceManager serviceManager)
{
serviceManager.ThrowIfNull(nameof(serviceManager));
serviceManager.RegisterResolver(
new HttpClientResolver()
.WithHttpMessageHandlerFactory((serviceProvider, categoryType) =>
{
var loggerType = typeof(ILogger<>).MakeGenericType(categoryType);
var logger = serviceProvider.GetService(loggerType).As<ILogger>();
var handler = new LoggingHttpMessageHandler(logger) { InnerHandler = new HttpClientHandler() };
return handler;
}));
serviceManager.RegisterOptionsManager<ApplicationConfigurationOptionsManager>();
}
public static void RegisterServices(IServiceProducer serviceProducer)
{
serviceProducer.ThrowIfNull(nameof(serviceProducer));
serviceProducer.RegisterSingleton<ICredentialManager, CredentialManager>();
serviceProducer.RegisterSingleton<ILoggingDatabase, FlatLoggingDatabase>();
serviceProducer.RegisterSingleton<ILogger, Logger>();
serviceProducer.RegisterSingleton<ApplicationLifetimeManager>();
serviceProducer.RegisterSingleton<ViewManager>();
serviceProducer.RegisterSingleton<IApplicationLauncher, ApplicationLauncher>();
serviceProducer.RegisterSingleton<IScreenshotProvider, ScreenshotProvider>();
serviceProducer.RegisterSingleton<IConfigurationManager, ConfigurationManager>();
serviceProducer.RegisterSingleton<IBloogumClient, BloogumClient>();
serviceProducer.RegisterSingleton<IApplicationUpdater, ApplicationUpdater>();
serviceProducer.RegisterSingleton<ILiteDatabase, LiteDatabase>(sp => new LiteDatabase("Daybreak.db"));
serviceProducer.RegisterSingleton<IMutexHandler, MutexHandler>();
serviceProducer.RegisterSingleton<IRuntimeStore, RuntimeStore>();
serviceProducer.RegisterSingleton<IBuildTemplateManager, BuildTemplateManager>();
serviceProducer.RegisterSingleton<IIconRetriever, IconRetriever>();
serviceProducer.RegisterSingleton<IShortcutManager, ShortcutManager>();
serviceProducer.RegisterScoped<ICredentialManager, CredentialManager>();
serviceProducer.RegisterScoped<IApplicationLauncher, ApplicationLauncher>();
serviceProducer.RegisterScoped<IScreenshotProvider, ScreenshotProvider>();
serviceProducer.RegisterScoped<IBloogumClient, BloogumClient>();
serviceProducer.RegisterScoped<IApplicationUpdater, ApplicationUpdater>();
serviceProducer.RegisterScoped<IBuildTemplateManager, BuildTemplateManager>();
serviceProducer.RegisterScoped<IIconRetriever, IconRetriever>();
serviceProducer.RegisterScoped<IPrivilegeManager, PrivilegeManager>();
serviceProducer.RegisterScoped<IScreenManager, ScreenManager>();
serviceProducer.RegisterLogWriter<ILogsManager, JsonLogsManager>();
}
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
applicationLifetimeProducer.ThrowIfNull(nameof(applicationLifetimeProducer));
applicationLifetimeProducer.RegisterService<ILoggingDatabase>();
applicationLifetimeProducer.RegisterService<IScreenshotProvider>();
applicationLifetimeProducer.RegisterService<IApplicationUpdater>();
}
public static void RegisterViews(IViewProducer viewProducer)
{
viewProducer.ThrowIfNull(nameof(viewProducer));
@@ -60,6 +77,10 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<ExecutablesView>();
viewProducer.RegisterView<BuildTemplateView>();
viewProducer.RegisterView<BuildsListView>();
viewProducer.RegisterView<RequestElevationView>();
viewProducer.RegisterView<ScreenChoiceView>();
viewProducer.RegisterView<VersionManagementView>();
viewProducer.RegisterView<LogsView>();
}
}
}
@@ -1,12 +1,12 @@
using Daybreak.Launch;
using Daybreak.Configuration;
using Daybreak.Models.Browser;
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Configuration;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.Threading.Tasks;
@@ -19,83 +19,56 @@ namespace Daybreak.Controls
/// <summary>
/// Interaction logic for ChromiumBrowserWrapper.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class ChromiumBrowserWrapper : UserControl
{
public static readonly DependencyProperty AddressProperty =
DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, string>(nameof(Address));
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 BrowserEnabledProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(BrowserEnabled));
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 readonly static DependencyProperty ControlsEnabledProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(ControlsEnabled), new PropertyMetadata(true));
public readonly static DependencyProperty CanNavigateProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(CanNavigate), new PropertyMetadata(true));
public readonly static DependencyProperty CanDownloadBuildProperty = DependencyPropertyExtensions.Register<ChromiumBrowserWrapper, bool>(nameof(CanDownloadBuild), new PropertyMetadata(false));
public event EventHandler<string> FavoriteUriChanged;
public event EventHandler MaximizeClicked;
public event EventHandler<Build> BuildDecoded;
private readonly IConfigurationManager configurationManager;
private readonly ILogger logger;
private readonly IBuildTemplateManager buildTemplateManager;
private ILiveOptions<ApplicationConfiguration> liveOptions;
private ILogger<ChromiumBrowserWrapper> logger;
private IBuildTemplateManager buildTemplateManager;
private CoreWebView2Environment coreWebView2Environment;
public bool CanDownloadBuild
{
get => this.GetTypedValue<bool>(CanDownloadBuildProperty);
set => this.SetTypedValue<bool>(CanDownloadBuildProperty, value);
}
public bool CanNavigate
{
get => this.GetTypedValue<bool>(CanNavigateProperty);
set => this.SetTypedValue<bool>(CanNavigateProperty, value);
}
[GenerateDependencyProperty(InitialValue = true)]
private bool canDownloadBuild;
[GenerateDependencyProperty(InitialValue = true)]
private bool canNavigate;
[GenerateDependencyProperty(InitialValue = true)]
private bool controlsEnabled;
[GenerateDependencyProperty]
private bool browserSupported;
[GenerateDependencyProperty]
private bool addressBarReadonly;
[GenerateDependencyProperty]
private bool browserEnabled;
[GenerateDependencyProperty]
private bool navigating;
[GenerateDependencyProperty]
private string favoriteAddress;
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);
set => this.SetTypedValue<bool>(AddressBarReadonlyProperty, value);
}
public bool BrowserEnabled
{
get => this.GetTypedValue<bool>(BrowserEnabledProperty);
private set => this.SetTypedValue<bool>(BrowserEnabledProperty, value);
}
public bool BrowserSupported
{
get => this.GetTypedValue<bool>(BrowserSupportedProperty);
private set => this.SetTypedValue<bool>(BrowserSupportedProperty, value);
}
public bool ControlsEnabled
{
get => this.GetTypedValue<bool>(ControlsEnabledProperty);
set => this.SetTypedValue<bool>(ControlsEnabledProperty, value);
set
{
if (this.BrowserSupported is true)
{
this.SetValue(AddressProperty, value);
}
}
}
public ChromiumBrowserWrapper()
{
this.configurationManager = Launcher.ApplicationServiceManager.GetService<IConfigurationManager>();
this.logger = Launcher.ApplicationServiceManager.GetService<ILogger>();
this.buildTemplateManager = Launcher.ApplicationServiceManager.GetService<IBuildTemplateManager>();
this.InitializeComponent();
this.InitializeEnvironment();
this.InitializeBrowser();
this.WebBrowser.IsEnabled = false;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
@@ -107,6 +80,18 @@ namespace Daybreak.Controls
}
}
public async void InitializeBrowser(
ILiveOptions<ApplicationConfiguration> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
{
this.liveOptions = liveOptions;
this.buildTemplateManager = buildTemplateManager;
this.logger = logger;
this.InitializeEnvironment();
await this.InitializeBrowser();
}
public async void ReinitializeBrowser()
{
await this.InitializeBrowser();
@@ -114,7 +99,7 @@ namespace Daybreak.Controls
private void InitializeEnvironment()
{
if (this.configurationManager.GetConfiguration().BrowsersEnabled is false)
if (this.liveOptions.Value.BrowsersEnabled is false)
{
this.BrowserSupported = false;
return;
@@ -136,9 +121,10 @@ namespace Daybreak.Controls
{
if (this.BrowserSupported is true)
{
this.WebBrowser.IsEnabled = true;
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.AddressBarReadonly = this.configurationManager.GetConfiguration().AddressBarReadonly;
this.CanDownloadBuild = this.configurationManager.GetConfiguration().ExperimentalFeatures.DynamicBuildLoading;
this.AddressBarReadonly = this.liveOptions.Value.AddressBarReadonly;
this.CanDownloadBuild = this.liveOptions.Value.ExperimentalFeatures.DynamicBuildLoading;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
this.WebBrowser.NavigationStarting += (browser, args) =>
{
@@ -209,8 +195,9 @@ namespace Daybreak.Controls
}
catch(Exception e)
{
this.logger.LogError(e);
this.logger.LogError(e, $"Exception encountered when deserializing {nameof(BrowserPayload)}");
}
if (payload?.Key == BrowserPayload.PayloadKeys.ContextMenu)
{
var contextMenuPayload = args.WebMessageAsJson.Deserialize<BrowserPayload<OnContextMenuPayload>>();
+14
View File
@@ -0,0 +1,14 @@
<UserControl x:Class="Daybreak.Controls.LogsGlyph"
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"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m7,3l0,-1.00209c0,-1.09965 0.89762,-1.99791 2.00488,-1.99791l0.99024,0c1.11098,0 2.00488,0.8945 2.00488,1.99791l0,1.00209l2.00442,0c0.55074,0 0.99558,0.44725 0.99558,0.99896l0,1.00208c0,0.5563 -0.44574,0.99896 -0.99558,0.99896l-9.00884,0c-0.55074,0 -0.99558,-0.44725 -0.99558,-0.99896l0,-1.00208c0,-0.5563 0.44574,-0.99896 0.99558,-0.99896l2.00442,0l0,0zm8.99999,1l1.00259,0c1.10649,0 1.99742,0.89704 1.99742,2.00359l0,20.99282c0,1.11383 -0.89428,2.00359 -1.99742,2.00359l-15.00516,0c-1.10649,0 -1.99742,-0.89704 -1.99742,-2.00359l0,-20.99282c0,-1.11383 0.89428,-2.00359 1.99742,-2.00359l1.00259,0c-0.00001,0.00163 -0.00001,0.00325 -0.00001,0.00488l0,0.99024c0,1.10726 0.89354,2.00488 2.00276,2.00488l8.99448,0c1.10609,0 2.00276,-0.8939 2.00276,-2.00488l0,-0.99024c0,-0.00163 0,-0.00325 -0.00001,-0.00488l0,0l0,0zm-6.49999,-1c0.27614,0 0.5,-0.22386 0.5,-0.5c0,-0.27614 -0.22386,-0.5 -0.5,-0.5c-0.27614,0 -0.5,0.22386 -0.5,0.5c0,0.27614 0.22386,0.5 0.5,0.5l0,0zm-6.5,8l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0zm0,3l0,1l13,0l0,-1l-13,0l0,0z"></Path>
</Viewbox>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for LogsGlyph.xaml
/// </summary>
public partial class LogsGlyph : UserControl
{
public LogsGlyph()
{
InitializeComponent();
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<UserControl x:Class="Daybreak.Controls.StaticGlyph"
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"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Polygon Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Points="209.89999389648438,829.800048828125 279.1999816894531,493.9000244140625 0,493.9000244140625 415.89996337890625,0 346.699951171875,335.8999938964844 625.800048828125,335.8999938964844"></Polygon>
</Viewbox>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for StaticGlyph.xaml
/// </summary>
public partial class StaticGlyph : UserControl
{
public StaticGlyph()
{
this.InitializeComponent();
}
}
}
+3 -4
View File
@@ -1,5 +1,4 @@
using Daybreak.Utils;
using System;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@@ -39,7 +38,7 @@ namespace Daybreak.Controls
}
nextVisible.Source = imageSource;
this.Transition(currentVisible, nextVisible);
Transition(currentVisible, nextVisible);
}
private Image CurrentVisible()
@@ -64,7 +63,7 @@ namespace Daybreak.Controls
return this.Image1;
}
}
private void Transition(Image from, Image to)
private static void Transition(Image from, Image to)
{
to.Visibility = Visibility.Visible;
to.Opacity = 0;
@@ -10,37 +10,21 @@ namespace Daybreak.Controls
/// <summary>
/// Interaction logic for AccountTemplate.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class AccountTemplate : UserControl
{
public static readonly DependencyProperty UsernameProperty = DependencyPropertyExtensions.Register<AccountTemplate, string>(nameof(Username));
public static readonly DependencyProperty CharacterNameProperty = DependencyPropertyExtensions.Register<AccountTemplate, string>(nameof(CharacterName));
public static readonly DependencyProperty PasswordProperty = DependencyPropertyExtensions.Register<AccountTemplate, string>(nameof(Password));
public static readonly DependencyProperty IsDefaultProperty = DependencyPropertyExtensions.Register<AccountTemplate, bool>(nameof(IsDefault));
public event EventHandler RemoveClicked;
public event EventHandler DefaultClicked;
public string Username
{
get => this.GetTypedValue<string>(UsernameProperty);
set => this.SetValue(UsernameProperty, value);
}
public string Password
{
get => this.GetTypedValue<string>(PasswordProperty);
set => this.SetValue(PasswordProperty, value);
}
public string CharacterName
{
get => this.GetTypedValue<string>(CharacterNameProperty);
set => this.SetValue(CharacterNameProperty, value);
}
public bool IsDefault
{
get => this.GetTypedValue<bool>(IsDefaultProperty);
set => this.SetValue(IsDefaultProperty, value);
}
[GenerateDependencyProperty]
private string username;
[GenerateDependencyProperty]
private string password;
[GenerateDependencyProperty]
private string characterName;
[GenerateDependencyProperty]
private bool isDefault;
public AccountTemplate()
{
@@ -10,26 +10,17 @@ namespace Daybreak.Controls
/// <summary>
/// Interaction logic for AttributeTemplate.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class AttributeTemplate : UserControl
{
public readonly static DependencyProperty CanAddProperty =
DependencyPropertyExtensions.Register<AttributeTemplate, bool>(nameof(CanAdd), new PropertyMetadata(false));
public readonly static DependencyProperty CanSubtractProperty =
DependencyPropertyExtensions.Register<AttributeTemplate, bool>(nameof(CanSubtract), new PropertyMetadata(false));
public event EventHandler<AttributeEntry> HelpClicked;
public bool CanAdd
{
get => this.GetTypedValue<bool>(CanAddProperty);
private set => this.SetValue(CanAddProperty, value);
}
public bool CanSubtract
{
get => this.GetTypedValue<bool>(CanSubtractProperty);
private set => this.SetValue(CanSubtractProperty, value);
}
public event EventHandler<AttributeEntry> AttributeChanged;
[GenerateDependencyProperty(InitialValue = false)]
private bool canAdd;
[GenerateDependencyProperty(InitialValue = false)]
private bool canSubtract;
public AttributeTemplate()
{
@@ -60,6 +51,7 @@ namespace Daybreak.Controls
this.DataContext.As<AttributeEntry>().Points--;
this.CanSubtract = this.DataContext.As<AttributeEntry>().Points > 0;
this.CanAdd = true;
this.AttributeChanged?.Invoke(this, this.DataContext.As<AttributeEntry>());
}
}
@@ -70,6 +62,7 @@ namespace Daybreak.Controls
this.DataContext.As<AttributeEntry>().Points++;
this.CanAdd = this.DataContext.As<AttributeEntry>().Points < 12;
this.CanSubtract = true;
this.AttributeChanged?.Invoke(this, this.DataContext.As<AttributeEntry>());
}
}
@@ -75,7 +75,7 @@
HorizontalContentAlignment="Stretch" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<local:AttributeTemplate HelpClicked="AttributeTemplate_HelpClicked"></local:AttributeTemplate>
<local:AttributeTemplate HelpClicked="AttributeTemplate_HelpClicked" AttributeChanged="AttributeTemplate_AttributeChanged"></local:AttributeTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
@@ -95,42 +95,42 @@
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<local:SkillTemplate Grid.Column="0"
<local:SkillTemplate Grid.Column="0" x:Name="SkillTemplate0"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill0, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="1"
<local:SkillTemplate Grid.Column="1" x:Name="SkillTemplate1"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill1, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="2"
<local:SkillTemplate Grid.Column="2" x:Name="SkillTemplate2"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill2, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="3"
<local:SkillTemplate Grid.Column="3" x:Name="SkillTemplate3"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill3, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="4"
<local:SkillTemplate Grid.Column="4" x:Name="SkillTemplate4"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill4, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="5"
<local:SkillTemplate Grid.Column="5" x:Name="SkillTemplate5"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill5, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="6"
<local:SkillTemplate Grid.Column="6" x:Name="SkillTemplate6"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill6, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
<local:SkillTemplate Grid.Column="7"
<local:SkillTemplate Grid.Column="7" x:Name="SkillTemplate7"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill7, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
+148 -107
View File
@@ -1,103 +1,57 @@
using Daybreak.Launch;
using Daybreak.Configuration;
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.IconRetrieve;
using System.Collections;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for BuildTemplate.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class BuildTemplate : UserControl
{
private const string InfoNamePlaceholder = "[NAME]";
private const string BaseAddress = $"https://wiki.guildwars.com/wiki/{InfoNamePlaceholder}";
public readonly static DependencyProperty PrimaryProfessionProperty =
DependencyPropertyExtensions.Register<BuildTemplate, Profession>(nameof(PrimaryProfession), new PropertyMetadata(Profession.None));
public readonly static DependencyProperty SecondaryProfessionProperty =
DependencyPropertyExtensions.Register<BuildTemplate, Profession>(nameof(SecondaryProfession), new PropertyMetadata(Profession.None));
public readonly static DependencyProperty Skill0Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill0), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill1Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill1), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill2Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill2), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill3Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill3), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill4Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill4), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill5Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill5), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill6Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill6), new PropertyMetadata(Skill.NoSkill));
public readonly static DependencyProperty Skill7Property =
DependencyPropertyExtensions.Register<BuildTemplate, Skill>(nameof(Skill7), new PropertyMetadata(Skill.NoSkill));
private bool suppressBuildChanged = false;
private bool loadedProperties = false;
private BuildEntry loadedBuild;
private SkillTemplate selectingSkillTemplate;
public Profession PrimaryProfession
{
get => this.GetTypedValue<Profession>(PrimaryProfessionProperty);
set => this.SetValue(PrimaryProfessionProperty, value);
}
public Profession SecondaryProfession
{
get => this.GetTypedValue<Profession>(SecondaryProfessionProperty);
set => this.SetValue(SecondaryProfessionProperty, value);
}
public Skill Skill0
{
get => this.GetTypedValue<Skill>(Skill0Property);
set => this.SetValue(Skill0Property, value);
}
public Skill Skill1
{
get => this.GetTypedValue<Skill>(Skill1Property);
set => this.SetValue(Skill1Property, value);
}
public Skill Skill2
{
get => this.GetTypedValue<Skill>(Skill2Property);
set => this.SetValue(Skill2Property, value);
}
public Skill Skill3
{
get => this.GetTypedValue<Skill>(Skill3Property);
set => this.SetValue(Skill3Property, value);
}
public Skill Skill4
{
get => this.GetTypedValue<Skill>(Skill4Property);
set => this.SetValue(Skill4Property, value);
}
public Skill Skill5
{
get => this.GetTypedValue<Skill>(Skill5Property);
set => this.SetValue(Skill5Property, value);
}
public Skill Skill6
{
get => this.GetTypedValue<Skill>(Skill6Property);
set => this.SetValue(Skill6Property, value);
}
public Skill Skill7
{
get => this.GetTypedValue<Skill>(Skill7Property);
set => this.SetValue(Skill7Property, value);
}
public event EventHandler BuildChanged;
[GenerateDependencyProperty]
private Profession primaryProfession;
[GenerateDependencyProperty]
private Profession secondaryProfession;
[GenerateDependencyProperty]
private Skill skill0;
[GenerateDependencyProperty]
private Skill skill1;
[GenerateDependencyProperty]
private Skill skill2;
[GenerateDependencyProperty]
private Skill skill3;
[GenerateDependencyProperty]
private Skill skill4;
[GenerateDependencyProperty]
private Skill skill5;
[GenerateDependencyProperty]
private Skill skill6;
[GenerateDependencyProperty]
private Skill skill7;
public ObservableCollection<Skill> AvailableSkills { get; } = new ObservableCollection<Skill>();
public ObservableCollection<AttributeEntry> Attributes { get; } = new ObservableCollection<AttributeEntry>();
public ObservableCollection<Profession> Professions { get; } = new ObservableCollection<Profession>(Profession.Professions);
@@ -105,12 +59,35 @@ namespace Daybreak.Controls
public BuildTemplate()
{
this.InitializeComponent();
this.DataContextChanged += BuildTemplate_DataContextChanged;
this.InitializeProperties();
this.DataContextChanged += this.BuildTemplate_DataContextChanged;
}
public void InitializeTemplate(
IIconRetriever iconRetriever,
ILiveOptions<ApplicationConfiguration> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
{
this.SkillBrowser.InitializeBrowser(liveOptions, buildTemplateManager, logger);
this.SkillTemplate0.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate1.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate2.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate3.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate4.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate5.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate6.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate7.InitializeSkillTemplate(iconRetriever);
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (this.loadedProperties is false)
{
return;
}
if (e.Property == PrimaryProfessionProperty || e.Property == SecondaryProfessionProperty)
{
if (e.Property == PrimaryProfessionProperty)
@@ -121,9 +98,52 @@ namespace Daybreak.Controls
{
this.loadedBuild.Build.Secondary = this.SecondaryProfession;
}
this.LoadSkills();
this.LoadAttributes();
if (this.suppressBuildChanged is false)
{
this.BuildChanged?.Invoke(this, new EventArgs());
}
}
if (e.Property == Skill0Property ||
e.Property == Skill1Property ||
e.Property == Skill2Property ||
e.Property == Skill3Property ||
e.Property == Skill4Property ||
e.Property == Skill5Property ||
e.Property == Skill6Property ||
e.Property == Skill7Property)
{
if (this.suppressBuildChanged is false)
{
this.loadedBuild.Build.Skills[0] = this.Skill0;
this.loadedBuild.Build.Skills[1] = this.Skill1;
this.loadedBuild.Build.Skills[2] = this.Skill2;
this.loadedBuild.Build.Skills[3] = this.Skill3;
this.loadedBuild.Build.Skills[4] = this.Skill4;
this.loadedBuild.Build.Skills[5] = this.Skill5;
this.loadedBuild.Build.Skills[6] = this.Skill6;
this.loadedBuild.Build.Skills[7] = this.Skill7;
this.BuildChanged?.Invoke(this, new EventArgs());
}
}
}
private void InitializeProperties()
{
this.PrimaryProfession = Profession.None;
this.SecondaryProfession = Profession.None;
this.Skill0 = Skill.NoSkill;
this.Skill1 = Skill.NoSkill;
this.Skill2 = Skill.NoSkill;
this.Skill3 = Skill.NoSkill;
this.Skill4 = Skill.NoSkill;
this.Skill5 = Skill.NoSkill;
this.Skill6 = Skill.NoSkill;
this.Skill7 = Skill.NoSkill;
this.loadedProperties = true;
}
private void BuildTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
@@ -174,55 +194,62 @@ namespace Daybreak.Controls
private void LoadSkills()
{
var possibleSkills = Skill.Skills
.Where(s => s.Profession == PrimaryProfession || s.Profession == SecondaryProfession || s.Profession == Profession.None)
.Where(s => s.Profession == this.PrimaryProfession || s.Profession == this.SecondaryProfession || s.Profession == Profession.None)
.Where(s => s != Skill.NoSkill)
.OrderBy(s => s.Name);
this.AvailableSkills.ClearAnd().AddRange(possibleSkills);
if (this.Skill0.Profession != PrimaryProfession &&
this.Skill0.Profession != SecondaryProfession &&
if (this.Skill0.Profession != this.PrimaryProfession &&
this.Skill0.Profession != this.SecondaryProfession &&
this.Skill0.Profession != Profession.None)
{
this.Skill0 = Skill.NoSkill;
}
if (this.Skill1.Profession != PrimaryProfession &&
this.Skill1.Profession != SecondaryProfession &&
if (this.Skill1.Profession != this.PrimaryProfession &&
this.Skill1.Profession != this.SecondaryProfession &&
this.Skill1.Profession != Profession.None)
{
this.Skill1 = Skill.NoSkill;
}
if (this.Skill2.Profession != PrimaryProfession &&
this.Skill2.Profession != SecondaryProfession &&
if (this.Skill2.Profession != this.PrimaryProfession &&
this.Skill2.Profession != this.SecondaryProfession &&
this.Skill2.Profession != Profession.None)
{
this.Skill2 = Skill.NoSkill;
}
if (this.Skill3.Profession != PrimaryProfession &&
this.Skill3.Profession != SecondaryProfession &&
if (this.Skill3.Profession != this.PrimaryProfession &&
this.Skill3.Profession != this.SecondaryProfession &&
this.Skill3.Profession != Profession.None)
{
this.Skill3 = Skill.NoSkill;
}
if (this.Skill4.Profession != PrimaryProfession &&
this.Skill4.Profession != SecondaryProfession &&
if (this.Skill4.Profession != this.PrimaryProfession &&
this.Skill4.Profession != this.SecondaryProfession &&
this.Skill4.Profession != Profession.None)
{
this.Skill4 = Skill.NoSkill;
}
if (this.Skill5.Profession != PrimaryProfession &&
this.Skill5.Profession != SecondaryProfession &&
if (this.Skill5.Profession != this.PrimaryProfession &&
this.Skill5.Profession != this.SecondaryProfession &&
this.Skill5.Profession != Profession.None)
{
this.Skill5 = Skill.NoSkill;
}
if (this.Skill6.Profession != PrimaryProfession &&
this.Skill6.Profession != SecondaryProfession &&
if (this.Skill6.Profession != this.PrimaryProfession &&
this.Skill6.Profession != this.SecondaryProfession &&
this.Skill6.Profession != Profession.None)
{
this.Skill6 = Skill.NoSkill;
}
if (this.Skill7.Profession != PrimaryProfession &&
this.Skill7.Profession != SecondaryProfession &&
if (this.Skill7.Profession != this.PrimaryProfession &&
this.Skill7.Profession != this.SecondaryProfession &&
this.Skill7.Profession != Profession.None)
{
this.Skill7 = Skill.NoSkill;
@@ -231,6 +258,7 @@ namespace Daybreak.Controls
private void LoadBuild()
{
this.suppressBuildChanged = true;
var build = this.DataContext.As<BuildEntry>();
this.loadedBuild = build;
this.PrimaryProfession = build.Build.Primary;
@@ -243,6 +271,7 @@ namespace Daybreak.Controls
this.Skill5 = build.Build.Skills[5];
this.Skill6 = build.Build.Skills[6];
this.Skill7 = build.Build.Skills[7];
this.suppressBuildChanged = false;
}
private void BrowseToInfo(string infoName)
@@ -254,13 +283,19 @@ namespace Daybreak.Controls
private void ShowInfoBrowser()
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
if (this.SkillBrowser.BrowserSupported is true)
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
}
}
private void HideInfoBrowser()
{
this.SkillBrowser.Width = 0;
if (this.SkillBrowser.BrowserSupported is true)
{
this.SkillBrowser.Width = 0;
}
}
private void ShowSkillListView()
@@ -281,7 +316,7 @@ namespace Daybreak.Controls
return;
}
this.BrowseToInfo(PrimaryProfession.Name);
this.BrowseToInfo(this.PrimaryProfession.Name);
if (e is RoutedEventArgs routedEventArgs)
{
routedEventArgs.Handled = true;
@@ -307,6 +342,11 @@ namespace Daybreak.Controls
this.BrowseToInfo(e.Attribute.Name);
}
private void AttributeTemplate_AttributeChanged(object sender, AttributeEntry e)
{
this.BuildChanged?.Invoke(this, new EventArgs());
}
private void SkillTemplate_Clicked(object sender, RoutedEventArgs e)
{
var skill = sender.As<SkillTemplate>().DataContext.As<Skill>();
@@ -319,6 +359,7 @@ namespace Daybreak.Controls
{
this.BrowseToInfo(skill.Name);
}
e.Handled = true;
}
@@ -337,14 +378,14 @@ namespace Daybreak.Controls
this.selectingSkillTemplate.DataContext = sender.As<ListView>().SelectedItem;
this.HideSkillListView();
this.loadedBuild.Build.Skills[0] = Skill0;
this.loadedBuild.Build.Skills[1] = Skill1;
this.loadedBuild.Build.Skills[2] = Skill2;
this.loadedBuild.Build.Skills[3] = Skill3;
this.loadedBuild.Build.Skills[4] = Skill4;
this.loadedBuild.Build.Skills[5] = Skill5;
this.loadedBuild.Build.Skills[6] = Skill6;
this.loadedBuild.Build.Skills[7] = Skill7;
this.loadedBuild.Build.Skills[0] = this.Skill0;
this.loadedBuild.Build.Skills[1] = this.Skill1;
this.loadedBuild.Build.Skills[2] = this.Skill2;
this.loadedBuild.Build.Skills[3] = this.Skill3;
this.loadedBuild.Build.Skills[4] = this.Skill4;
this.loadedBuild.Build.Skills[5] = this.Skill5;
this.loadedBuild.Build.Skills[6] = this.Skill6;
this.loadedBuild.Build.Skills[7] = this.Skill7;
}
private void ListView_NavigateWithMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
@@ -11,24 +11,17 @@ namespace Daybreak.Controls
/// <summary>
/// Interaction logic for GuildwarsPathTemplate.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class GuildwarsPathTemplate : UserControl
{
public static readonly DependencyProperty PathProperty = DependencyPropertyExtensions.Register<GuildwarsPathTemplate, string>(nameof(Path));
public static readonly DependencyProperty IsDefaultProperty = DependencyPropertyExtensions.Register<GuildwarsPathTemplate, bool>(nameof(IsDefault));
public event EventHandler RemoveClicked;
public event EventHandler DefaultClicked;
public string Path
{
get => this.GetTypedValue<string>(PathProperty);
set => this.SetValue(PathProperty, value);
}
public bool IsDefault
{
get => this.GetTypedValue<bool>(IsDefaultProperty);
set => this.SetValue(IsDefaultProperty, value);
}
[GenerateDependencyProperty]
private string path;
[GenerateDependencyProperty]
private bool isDefault;
public GuildwarsPathTemplate()
{
@@ -0,0 +1,18 @@
<UserControl x:Class="Daybreak.Controls.LogMessageTemplate"
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="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch">
<TextBlock Text="{Binding ElementName=_this, Path=Message, Mode=OneWay}" Background="Transparent"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
MouseLeftButtonDown="TextBox_MouseLeftButtonDown" MaxHeight="18" TextWrapping="Wrap"></TextBlock>
</Grid>
</UserControl>
@@ -0,0 +1,36 @@
using System.Windows.Extensions;
using System.Windows.Controls;
using System.Windows.Input;
using System.Extensions;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for LogTemplate.xaml
/// </summary>
public partial class LogMessageTemplate : UserControl
{
private bool expanded;
[GenerateDependencyProperty]
private string message;
public LogMessageTemplate()
{
this.InitializeComponent();
}
private void TextBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs eventArgs)
{
this.expanded = !this.expanded;
if (this.expanded)
{
sender.As<TextBlock>().MaxHeight = double.MaxValue;
}
else
{
sender.As<TextBlock>().MaxHeight = 18;
}
}
}
}
@@ -0,0 +1,17 @@
<UserControl x:Class="Daybreak.Controls.Templates.ScreenTemplate"
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.Templates"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Rectangle Stroke="{Binding ElementName=_this, Path=Highlight, Mode=OneWay}" StrokeThickness="15"></Rectangle>
<TextBlock FontSize="168" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{Binding ElementName=_this, Path=ScreenId, Mode=OneWay}"
Foreground="{Binding ElementName=_this, Path=Highlight, Mode=OneWay}"></TextBlock>
<Rectangle Fill="Transparent" MouseEnter="Rectangle_MouseEnter" MouseLeave="Rectangle_MouseLeave" MouseLeftButtonDown="Rectangle_MouseLeftButtonDown"></Rectangle>
</Grid>
</UserControl>
@@ -0,0 +1,63 @@
using Daybreak.Models;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
namespace Daybreak.Controls.Templates
{
/// <summary>
/// Interaction logic for ScreenTemplate.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class ScreenTemplate : UserControl
{
public event EventHandler<Screen> Clicked;
[GenerateDependencyProperty]
private string screenId;
[GenerateDependencyProperty]
private Brush highlight;
public ScreenTemplate()
{
this.InitializeComponent();
this.DataContextChanged += ScreenTemplate_DataContextChanged;
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ForegroundProperty)
{
this.Highlight = e.NewValue.As<Brush>();
}
}
private void ScreenTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is Screen screen)
{
this.ScreenId = screen.Id.ToString();
}
}
private void Rectangle_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
this.Highlight = Brushes.LightSteelBlue;
}
private void Rectangle_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
this.Highlight = this.Foreground;
}
private void Rectangle_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.Clicked?.Invoke(this, this.DataContext.As<Screen>());
}
}
}
@@ -16,47 +16,42 @@ namespace Daybreak.Controls
/// <summary>
/// Interaction logic for SkillTemplate.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class SkillTemplate : UserControl
{
public readonly static DependencyProperty ImageSourceProperty =
DependencyPropertyExtensions.Register<SkillTemplate, ImageSource>(nameof(ImageSource));
public readonly static DependencyProperty BorderOpacityProperty =
DependencyPropertyExtensions.Register<SkillTemplate, double>(nameof(BorderOpacity), new PropertyMetadata(0d));
public event EventHandler<RoutedEventArgs> Clicked;
public event EventHandler RemoveClicked;
private readonly IIconRetriever iconRetriever;
private IIconRetriever iconRetriever;
public ImageSource ImageSource
{
get => this.GetTypedValue<ImageSource>(ImageSourceProperty);
set => this.SetValue(ImageSourceProperty, value);
}
public double BorderOpacity
{
get => this.GetTypedValue<double>(BorderOpacityProperty);
set => this.SetValue(BorderOpacityProperty, value);
}
[GenerateDependencyProperty]
private ImageSource imageSource;
[GenerateDependencyProperty]
private double borderOpacity;
public SkillTemplate()
{
this.iconRetriever = Launcher.ApplicationServiceManager.GetService<IIconRetriever>();
this.InitializeComponent();
this.DataContextChanged += SkillTemplate_DataContextChanged;
}
public void InitializeSkillTemplate(IIconRetriever iconRetriever)
{
this.iconRetriever = iconRetriever;
}
private void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is Skill skill)
{
if (skill != Skill.NoSkill)
{
Task.Run(() => GetImageStream(skill)).ContinueWith((previousTask) =>
Task.Run(() => this.GetImageStream(skill)).ContinueWith((previousTask) =>
{
this.Dispatcher.Invoke(() =>
{
this.ImageSource = GetImageSource(previousTask.Result);
this.ImageSource = this.GetImageSource(previousTask.Result);
});
});
}
@@ -97,6 +92,11 @@ namespace Daybreak.Controls
}
private async Task<Stream> GetImageStream(Skill skill)
{
if (this.iconRetriever is null)
{
return null;
}
var maybeStream = await this.iconRetriever.GetIcon(skill);
return maybeStream.ExtractValue();
}
+19 -6
View File
@@ -9,19 +9,28 @@
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<Version>0.7.7</Version>
<ApplicationManifest>app.manifest</ApplicationManifest>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.2.5</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.818.41" />
<PackageReference Include="LiteDB" Version="5.0.10" />
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.864.35" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Slim" Version="1.2.1" />
<PackageReference Include="NReco.Logging.File" Version="1.1.1" />
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
<PackageReference Include="Slim" Version="1.5.1" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.1.4" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.3.0" />
<PackageReference Include="SystemExtensions.NetStandard.DependencyInjection" Version="1.1.0" />
<PackageReference Include="WCL" Version="1.0.2" />
<PackageReference Include="WpfExtended" Version="0.2.0" />
<PackageReference Include="WpfExtended" Version="0.6.0" />
<PackageReference Include="WpfExtended.SourceGeneration" Version="0.1.1" />
<PackageReference Include="WpfScreenHelper" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
@@ -62,4 +71,8 @@
</Page>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'">
<Exec Command="echo.&gt;$(Version).version" />
</Target>
</Project>
+30 -13
View File
@@ -1,17 +1,13 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Services.Logging;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Microsoft.Extensions.Logging;
using Slim;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Extensions;
@@ -19,7 +15,7 @@ namespace Daybreak.Launch
{
public sealed class Launcher : ExtendedApplication<MainWindow>
{
public static IServiceManager ApplicationServiceManager { get; private set; }
private ILogger logger;
private readonly static Launcher launcher = new();
[STAThread]
@@ -28,10 +24,14 @@ namespace Daybreak.Launch
return LaunchMainWindow();
}
protected override void SetupServiceManager(IServiceManager serviceManager)
{
ProjectConfiguration.RegisterResolvers(serviceManager);
}
protected override void RegisterServices(IServiceProducer serviceProducer)
{
ProjectConfiguration.RegisterServices(this.ServiceManager);
ProjectConfiguration.RegisterLifetimeServices(this.ServiceManager.GetService<IApplicationLifetimeManager>());
ServiceManager.BuildSingletons();
ProjectConfiguration.RegisterViews(this.ServiceManager.GetService<IViewManager>());
}
protected override bool HandleException(Exception e)
@@ -41,10 +41,18 @@ namespace Daybreak.Launch
return false;
}
this.ServiceManager.GetService<ILogger>().LogCritical(e);
if (e is FatalException fatalException)
{
this.logger.LogCritical(e, $"{nameof(FatalException)} encountered. Closing application");
MessageBox.Show(fatalException.ToString());
File.WriteAllText("crash.log", e.ToString());
return false;
}
else if (e is TargetInvocationException targetInvocationException && e.InnerException is FatalException innerFatalException)
{
this.logger.LogCritical(e, $"{nameof(FatalException)} encountered. Closing application");
MessageBox.Show(innerFatalException.ToString());
File.WriteAllText("crash.log", e.ToString());
return false;
}
else if (e is AggregateException aggregateException)
@@ -54,24 +62,33 @@ namespace Daybreak.Launch
{
/*
* Ignore exception caused by browser failing to initialize due to missing window.
* Likely caused by switching windows before browser was initialized.
* Likely caused by switching views before browser was initialized.
*/
this.logger.LogError(e, "Failed to initialize browser");
return true;
}
}
else if (e.Message.Contains("Invalid window handle.") && e.StackTrace.Contains("CoreWebView2Environment.CreateCoreWebView2ControllerAsync"))
{
/*
* Ignore exception caused by browser failing to initialize due to missing window.
* Likely caused by switching views before the browser was initialized.
*/
this.logger.LogError(e, "Failed to initialize browser");
return true;
}
this.logger.LogError(e, $"Unhandled exception caught {e.GetType()}");
MessageBox.Show(e.ToString());
return true;
}
protected override void ApplicationStarting()
{
ApplicationServiceManager = this.ServiceManager;
this.ServiceManager.GetService<IApplicationLifetimeManager>().OnStartup();
this.logger = this.ServiceManager.GetService<ILogger<Launcher>>();
this.RegisterViewContainer();
}
protected override void ApplicationClosing()
{
this.ServiceManager.GetService<IApplicationLifetimeManager>().OnClosing();
}
private void RegisterViewContainer()
+52 -38
View File
@@ -16,43 +16,46 @@
Title="Daybreak"
d:DesignHeight="450" d:DesignWidth="800">
<Window.Resources>
<Style x:Key="Window_SettingsButton" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Rectangle x:Name="OverlayRect" Fill="{TemplateBinding Background}" Opacity="0"></Rectangle>
<Viewbox Margin="5" Stretch="Uniform">
<Grid>
<Path Stroke="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
<Style x:Key="Window_SettingsButton" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Rectangle x:Name="OverlayRect" Fill="{TemplateBinding Background}" Opacity="0"></Rectangle>
<Viewbox Margin="5" Stretch="Uniform">
<Grid>
<Path Stroke="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
Data="m76.89,45.018c0,-1.587 -0.151,-3.138 -0.383,-4.66l11.227,-6.48l-11.639,-20.136l-11.616,6.47c-2.421,-1.931 -5.479,-3.506 -8.479,-4.664l0,-11.548l-22,0l0,11.548c-4,1.112 -5.649,2.617 -8.005,4.448l-11.251,-6.604l-11.914,20.006l11.12,6.604c-0.262,1.643 -0.45,3.308 -0.45,5.016c0,1.587 0.153,3.136 0.376,4.658l-11.228,6.484l11.634,20.135l11.424,-6.472c2.431,1.933 4.294,3.506 8.294,4.665l0,12.512l22,0l0,-12.512c3,-1.115 5.839,-2.618 8.19,-4.449l11.348,6.604l11.96,-20.006l-11.095,-6.603c0.258,-1.64 0.487,-3.304 0.487,-5.016zm-31.704,14.137c-7.799,0 -14.138,-6.332 -14.138,-14.138c0,-7.808 6.339,-14.136 14.138,-14.136c7.81,0 14.14,6.328 14.14,14.136c-0.001,7.806 -6.331,14.138 -14.14,14.138z"></Path>
<Path Fill="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
<Path Fill="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"
Data="m56.5,89l-22,0c-1.381,0 -2.5,-1.119 -2.5,-2.5l0,-10.199c-2.612,-0.955 -4.464,-2.126 -6.274,-3.49l-10.229,5.671c-1.192,0.661 -2.694,0.244 -3.377,-0.936l-11.633,-20.135c-0.331,-0.574 -0.422,-1.257 -0.25,-1.897s0.591,-1.188 1.165,-1.519l9.794,-5.654c-0.121,-1.164 -0.181,-2.262 -0.181,-3.323c0,-1.317 0.112,-2.592 0.254,-3.721l-9.656,-5.75c-0.57,-0.34 -0.982,-0.893 -1.145,-1.537c-0.163,-0.644 -0.063,-1.326 0.279,-1.896l11.977,-20.006c0.702,-1.173 2.218,-1.564 3.399,-0.878l9.927,5.762c1.618,-1.213 3.362,-2.346 5.949,-3.273l0,-10.219c0.001,-1.381 1.12,-2.5 2.501,-2.5l22,0c1.381,0 2.5,1.119 2.5,2.5l0,10.377c2.124,0.938 4.104,2.059 5.919,3.35l9.939,-5.657c1.19,-0.677 2.713,-0.269 3.4,0.922l11.639,20.135c0.332,0.574 0.423,1.257 0.251,1.897s-0.591,1.188 -1.165,1.519l-9.781,5.646c0.126,1.165 0.188,2.266 0.188,3.329c0,1.327 -0.108,2.597 -0.246,3.717l9.692,5.752c0.571,0.339 0.983,0.891 1.147,1.533c0.163,0.644 0.065,1.325 -0.274,1.896l-11.897,20.005c-0.703,1.183 -2.234,1.573 -3.416,0.876l-9.798,-5.765c-1.782,1.25 -3.634,2.29 -5.598,3.144l0,10.324c0,1.381 -1.119,2.5 -2.5,2.5zm-19.5,-5l17,0l0,-9.512c0,-1.045 0.649,-1.979 1.629,-2.344c2.624,-0.975 5.004,-2.309 7.275,-4.078c0.809,-0.629 1.921,-0.702 2.804,-0.182l9.078,5.341l9.347,-15.716l-8.974,-5.325c-0.88,-0.522 -1.354,-1.528 -1.194,-2.538c0.198,-1.259 0.425,-2.923 0.425,-4.629c0,-1.314 -0.116,-2.716 -0.354,-4.284c-0.154,-1.018 0.33,-2.026 1.222,-2.541l9.061,-5.23l-9.145,-15.818l-9.208,5.241c-0.888,0.505 -1.995,0.42 -2.795,-0.219c-2.199,-1.754 -4.747,-3.196 -7.57,-4.286c-0.964,-0.372 -1.6,-1.299 -1.6,-2.332l0,-9.548l-17.001,0l0,9.548c0,1.123 -0.748,2.107 -1.83,2.408c-3.47,0.965 -5.073,2.212 -7.293,3.938l-0.098,0.076c-0.804,0.625 -1.909,0.7 -2.789,0.188l-9.243,-5.365l-9.423,15.739l8.937,5.321c0.879,0.524 1.351,1.531 1.189,2.542c-0.202,1.27 -0.435,2.943 -0.435,4.622c0,1.318 0.112,2.724 0.342,4.297c0.148,1.013 -0.337,2.015 -1.224,2.526l-9.065,5.234l9.151,15.842l9.525,-5.28c0.883,-0.49 1.976,-0.4 2.768,0.229c2.384,1.896 4.207,3.214 7.684,4.221c1.069,0.31 1.804,1.29 1.804,2.402l0,9.512zm8.186,-22.345c-9.174,0 -16.638,-7.464 -16.638,-16.638c0,-9.173 7.464,-16.636 16.638,-16.636c9.175,0 16.64,7.463 16.64,16.636c-0.001,9.174 -7.466,16.638 -16.64,16.638zm0,-28.273c-6.417,0 -11.638,5.22 -11.638,11.636c0,6.417 5.221,11.638 11.638,11.638c6.418,0 11.64,-5.221 11.64,-11.638c-0.001,-6.416 -5.222,-11.636 -11.64,-11.636z"></Path>
</Grid>
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0.5"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid>
</Viewbox>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0.5"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Opacity" TargetName="OverlayRect" Value="0"></Setter>
</MultiTrigger.Setters>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid Background="Black">
<Grid.RowDefinitions>
@@ -82,9 +85,20 @@
Text="{Binding ElementName=_this, Path=CreditText, Mode=OneWay}" VerticalAlignment="Bottom"
HorizontalAlignment="Right" Margin="0, 0, 20, 30" FontSize="22" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Clicked="CreditTextBox_MouseLeftButtonDown" Cursor="Hand"></controls:OpaqueButton>
<TextBox Grid.Row="1" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Opacity="0.6"
Text="{Binding ElementName=_this, Path=CurrentVersionText, Mode=OneWay}" IsReadOnly="True" VerticalAlignment="Bottom"
HorizontalAlignment="Right" Margin="0, 0, 20, 10" FontSize="10" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></TextBox>
<WrapPanel Grid.Row="1" Margin="0, 0, 20, 10" VerticalAlignment="Bottom" HorizontalAlignment="Right">
<controls:OpaqueButton Grid.Row="1" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Opacity="0.6"
Text="{Binding ElementName=_this, Path=CurrentVersionText, Mode=OneWay}" FontSize="10"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Clicked="VersionText_Clicked"></controls:OpaqueButton>
<controls:OpaqueButton Grid.Row="1" Background="Transparent" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Opacity="0.6" Text=" - " FontSize="10" BorderBrush="Transparent" BorderThickness="0"
Visibility="{Binding ElementName=_this, Path=IsRunningAsAdmin, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Clicked="VersionText_Clicked"></controls:OpaqueButton>
<controls:OpaqueButton Grid.Row="1" Background="Transparent" Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Opacity="0.6" Text="[Admin]" FontSize="10" BorderBrush="Transparent" BorderThickness="0"
Visibility="{Binding ElementName=_this, Path=IsRunningAsAdmin, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
Clicked="VersionText_Clicked"></controls:OpaqueButton>
</WrapPanel>
<Grid x:Name="Container" Grid.Row="1">
</Grid>
<wcl:Border OnResize="Border_OnResize" Grid.RowSpan="2" Active="True"></wcl:Border>
+28 -19
View File
@@ -1,11 +1,13 @@
using Daybreak.Services.Bloogum;
using Daybreak.Services.Runtime;
using Daybreak.Configuration;
using Daybreak.Services.Bloogum;
using Daybreak.Services.Privilege;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Pepa.Wpf.Utilities;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.Threading;
@@ -21,43 +23,45 @@ namespace Daybreak.Launch
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class MainWindow : Window
{
public static readonly DependencyProperty CreditTextProperty = DependencyPropertyExtensions.Register<MainWindow, string>(nameof(CreditText));
public static readonly DependencyProperty CurrentVersionTextProperty = DependencyPropertyExtensions.Register<MainWindow, string>(nameof(CurrentVersionText));
private readonly IViewManager viewManager;
private readonly IScreenshotProvider screenshotProvider;
private readonly IBloogumClient bloogumClient;
private readonly IApplicationUpdater applicationUpdater;
private readonly IPrivilegeManager privilegeManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly CancellationTokenSource cancellationToken = new();
public string CreditText
{
get => this.GetTypedValue<string>(CreditTextProperty);
set => this.SetValue(CreditTextProperty, value);
}
public string CurrentVersionText
{
get => this.GetTypedValue<string>(CurrentVersionTextProperty);
set => this.SetValue(CurrentVersionTextProperty, value);
}
[GenerateDependencyProperty]
private string creditText;
[GenerateDependencyProperty]
private string currentVersionText;
[GenerateDependencyProperty]
private bool isRunningAsAdmin;
public MainWindow(
IViewManager viewManager,
IScreenshotProvider screenshotProvider,
IBloogumClient bloogumClient,
IApplicationUpdater applicationUpdater)
IApplicationUpdater applicationUpdater,
IPrivilegeManager privilegeManager,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenshotProvider = screenshotProvider.ThrowIfNull(nameof(screenshotProvider));
this.bloogumClient = bloogumClient.ThrowIfNull(nameof(bloogumClient));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.InitializeComponent();
this.CurrentVersionText = this.applicationUpdater.CurrentVersion;
this.CurrentVersionText = this.applicationUpdater.CurrentVersion.ToString();
this.IsRunningAsAdmin = this.privilegeManager.AdminPrivileges;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.SetupImageCycle();
@@ -140,6 +144,11 @@ namespace Daybreak.Launch
}
}
private void VersionText_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<VersionManagementView>();
}
private void SetImage(ImageSource imageSource)
{
this.ImageViewer.ShowImage(imageSource);
@@ -160,7 +169,7 @@ namespace Daybreak.Launch
private async void CheckForUpdates()
{
var updateAvailable = await this.applicationUpdater.UpdateAvailable().ConfigureAwait(true);
var updateAvailable = this.liveOptions.Value.AutoCheckUpdate is true && await this.applicationUpdater.UpdateAvailable().ConfigureAwait(true);
if (updateAvailable)
{
this.viewManager.ShowView<AskUpdateView>();
+11
View File
@@ -0,0 +1,11 @@
using System;
namespace Daybreak.Models
{
public sealed class ElevationRequest
{
public object DataContext { get; set; }
public Type View { get; set; }
public string MessageToUser { get; set; }
}
}
+14
View File
@@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace Daybreak.Models.Github
{
public sealed class GithubRefTag
{
[JsonProperty("ref")]
public string Ref;
[JsonProperty("node_id")]
public string NodeId;
[JsonProperty("url")]
public string Url;
}
}
+11
View File
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Windows.Forms;
namespace Daybreak.Models
{
public sealed class KeyMacro
{
public List<Keys> Keys { get; set; }
public Keys TargetKey { get; set; }
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace Daybreak.Models
{
public sealed class KeyboardHookEventArgs
{
public bool Handled { get; set; }
public KeyboardState KeyboardState { get; }
public KeyboardInput KeyboardInput { get; }
public KeyboardHookEventArgs(
KeyboardState keyboardState,
KeyboardInput keyboardInput)
{
this.KeyboardState = keyboardState;
this.KeyboardInput = keyboardInput;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Daybreak.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct KeyboardInput
{
/// <summary>
/// A virtual-key code. The code must be a value in the range 1 to 254.
/// </summary>
public int VirtualCode;
// EDT: added a conversion from VirtualCode to Keys.
/// <summary>
/// The VirtualCode converted to typeof(Keys) for higher usability.
/// </summary>
public Keys Key { get { return (Keys)VirtualCode; } }
/// <summary>
/// A hardware scan code for the key.
/// </summary>
public int HardwareScanCode;
/// <summary>
/// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level.
/// </summary>
public int Flags;
/// <summary>
/// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message.
/// </summary>
public int TimeStamp;
/// <summary>
/// Additional information associated with the message.
/// </summary>
public IntPtr AdditionalInformation;
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Daybreak.Models
{
public enum KeyboardState
{
KeyDown = 0x0100,
KeyUp = 0x0101,
SysKeyDown = 0x0104,
SysKeyUp = 0x0105
}
}
+8 -5
View File
@@ -1,12 +1,15 @@
using System;
using Microsoft.Extensions.Logging;
using System;
namespace Daybreak.Models
{
public class Log
public sealed class Log
{
public LogLevel LogLevel { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public DateTime Timestamp { get; set; }
public string Category { get; set; }
public LogLevel LogLevel { get; set; }
public string CorrelationVector { get; set; }
public string EventId { get; set; }
public DateTime LogTime { get; set; }
}
}
-10
View File
@@ -1,10 +0,0 @@
namespace Daybreak.Models
{
public enum LogLevel
{
Information,
Warning,
Error,
Critical
}
}
+14
View File
@@ -0,0 +1,14 @@
using Microsoft.CorrelationVector;
namespace Daybreak.Models
{
public class ScopeMetadata
{
public CorrelationVector CorrelationVector { get; set; }
public ScopeMetadata(CorrelationVector correlationVector)
{
this.CorrelationVector = correlationVector;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.Windows;
namespace Daybreak.Models
{
public sealed class Screen
{
public int Id { get; set; }
public Rect Size { get; set; }
}
}
+3 -1
View File
@@ -5,9 +5,11 @@ namespace Daybreak.Models
public sealed class UpdateStatus : INotifyPropertyChanged
{
public static readonly UpdateStep StartingStep = new("Starting");
public static readonly UpdateStep InitializingDownload = new("Initializing download");
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.");
public static readonly UpdateStep DownloadFinished = new("Download finished. Application will restart in order to apply the update");
public static readonly UpdateStep FailedDownload = new("Update failed. Please check logs for details");
private UpdateStep currentStep = StartingStep;
+158
View File
@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Extensions;
using System.Linq;
namespace Daybreak.Models.Versioning
{
public sealed class Version
{
private List<VersionToken> parts = new();
public bool HasPrefix { get; private set; }
public IEnumerable<VersionToken> VersionTokens { get => this.parts; }
public string VersionString { get => this.ToString(); }
private Version()
{
}
public Version(string version)
{
if (TryParseParts(version, out var parts, out var hasPrefix) is false)
{
throw new ArgumentException($"Provided argument is not valid: {version}");
}
this.parts = parts;
this.HasPrefix = hasPrefix;
}
public override string ToString()
{
var version = string.Join('.', parts.OfType<VersionNumberToken>());
if (parts.Last() is VersionStringToken)
{
version += "-" + parts.Last();
}
if (this.HasPrefix)
{
version = 'v' + version;
}
return version;
}
public static Version Parse(string version)
{
if (TryParse(version, out var parsedVersion) is false)
{
throw new InvalidOperationException($"Unable to parse version: {version}");
}
return parsedVersion;
}
public static bool TryParse(string version, out Version parsedVersion)
{
if (TryParseParts(version, out var parts, out var hasPrefix) is false)
{
parsedVersion = null;
return false;
}
for (int i = parts.Count - 1; i >= 1; i--)
{
if (parts[i] is VersionNumberToken && parts[i].ToString() == "0")
{
parts.RemoveAt(i);
}
else
{
break;
}
}
parsedVersion = new Version
{
parts = parts,
HasPrefix = hasPrefix
};
return true;
}
private static bool TryParseParts(string version, out List<VersionToken> parts, out bool hasPrefix)
{
hasPrefix = false;
parts = null;
if (version.IsNullOrWhiteSpace())
{
return false;
}
if (version.StartsWith('.') || version.EndsWith('.'))
{
return false;
}
var tokens = version.Trim().Split('.');
if (tokens.Length == 0)
{
return false;
}
if (tokens[0].StartsWith('v'))
{
hasPrefix = true;
tokens[0] = tokens[0][1..];
}
parts = new List<VersionToken>();
for(int i = 0; i < tokens.Length - 1; i++)
{
var token = tokens[i];
if (token.IsNullOrWhiteSpace() || token.All(c => char.IsDigit(c)) is false)
{
return false;
}
parts.Add(VersionToken.Parse(token));
}
var lastToken = tokens[^1];
if (lastToken.Contains('-'))
{
var lastTokenParts = lastToken.Split('-');
if (lastTokenParts.Length > 2 || lastTokenParts.Length < 2)
{
return false;
}
if (lastTokenParts[0].IsNullOrWhiteSpace() || lastTokenParts[0].All(c => char.IsDigit(c)) is false)
{
return false;
}
parts.Add(VersionToken.Parse(lastTokenParts[0]));
if (lastTokenParts[1].IsNullOrWhiteSpace() || lastTokenParts[1].All(c => char.IsLetter(c)) is false)
{
return false;
}
parts.Add(VersionToken.Parse(lastTokenParts[1]));
}
else
{
if (lastToken.IsNullOrWhiteSpace() || lastToken.Any(c => char.IsDigit(c) is false))
{
return false;
}
parts.Add(VersionToken.Parse(lastToken));
}
return true;
}
}
}
@@ -0,0 +1,16 @@
namespace Daybreak.Models.Versioning
{
public sealed class VersionNumberToken : VersionToken
{
public int Number { get; }
internal VersionNumberToken(int number)
{
this.Number = number;
}
protected override string Stringify()
{
return this.Number.ToString();
}
}
}
@@ -0,0 +1,16 @@
namespace Daybreak.Models.Versioning
{
public sealed class VersionStringToken : VersionToken
{
public string Token { get; }
internal VersionStringToken(string token)
{
this.Token = token;
}
protected override string Stringify()
{
return this.Token;
}
}
}
@@ -0,0 +1,31 @@
using System;
using System.Linq;
namespace Daybreak.Models.Versioning
{
public abstract class VersionToken
{
public static VersionToken Parse(string token)
{
if (token.All(c => char.IsDigit(c)))
{
var number = int.Parse(token);
return new VersionNumberToken(number);
}
if (token.All(c => char.IsLetter(c)))
{
return new VersionStringToken(token);
}
throw new ArgumentException($"Failed to parse version token {token}");
}
protected abstract string Stringify();
public override string ToString()
{
return this.Stringify();
}
}
}
@@ -1,13 +1,15 @@
using Daybreak.Exceptions;
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Credentials;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Utils;
using Daybreak.Services.Privilege;
using Daybreak.Views;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.IO;
@@ -15,63 +17,75 @@ using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Daybreak.Services.ApplicationLauncher
{
public class ApplicationLauncher : IApplicationLauncher
{
private const int MaxRetries = 10;
private const string TexModProcessName = "TexMod";
private const string UModProcessName = "uMod";
private const string ToolboxProcessName = "GWToolbox";
private const string ProcessName = "gw";
private const string ArenaNetMutex = "AN-Mute";
private readonly IConfigurationManager configurationManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly ICredentialManager credentialManager;
private readonly IMutexHandler mutexHandler;
private readonly ILogger logger;
private readonly ILogger<ApplicationLauncher> logger;
private readonly IPrivilegeManager privilegeManager;
public bool IsTexmodRunning => TexModProcessDetected();
public bool IsGuildwarsRunning => this.GuildwarsProcessDetected();
public bool IsToolboxRunning => GuildwarsToolboxProcessDetected();
public ApplicationLauncher(
IConfigurationManager configurationManager,
ILiveOptions<ApplicationConfiguration> liveOptions,
ICredentialManager credentialManager,
IMutexHandler mutexHandler,
ILogger logger)
ILogger<ApplicationLauncher> logger,
IPrivilegeManager privilegeManager)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.mutexHandler = mutexHandler.ThrowIfNull(nameof(mutexHandler));
this.credentialManager = credentialManager.ThrowIfNull(nameof(credentialManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
}
public async Task LaunchGuildwars()
public async Task<bool> LaunchGuildwars()
{
var configuration = this.configurationManager.GetConfiguration();
var configuration = this.liveOptions.Value;
var auth = await this.credentialManager.GetDefaultCredentials().ConfigureAwait(false);
auth.Do(
onSome: (credentials) =>
return await auth.Switch(
onSome: async (credentials) =>
{
if (configuration.ExperimentalFeatures.MultiLaunchSupport is true)
{
ClearGwLocks();
if (this.privilegeManager.AdminPrivileges is false)
{
this.privilegeManager.RequestAdminPrivileges<MainView>("You need administrator rights in order to start using multi-launch");
return false;
}
this.ClearGwLocks();
}
LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
return await this.LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
},
onNone: () =>
{
throw new CredentialsNotFoundException($"No credentials available");
});
})
.ExtractValue();
}
public Task LaunchGuildwarsToolbox()
{
return Task.Run(() =>
{
var configuration = this.configurationManager.GetConfiguration();
var configuration = this.liveOptions.Value;
var executable = configuration.ToolboxPath;
if (File.Exists(executable) is false)
{
@@ -89,7 +103,7 @@ namespace Daybreak.Services.ApplicationLauncher
{
return Task.Run(() =>
{
var configuration = this.configurationManager.GetConfiguration();
var configuration = this.liveOptions.Value;
var executable = configuration.TexmodPath;
if (File.Exists(executable) is false)
{
@@ -103,9 +117,36 @@ namespace Daybreak.Services.ApplicationLauncher
});
}
private void LaunchGuildwarsProcess(string email, Models.SecureString password, string character)
public void RestartDaybreakAsAdmin()
{
var executable = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
this.logger.LogInformation("Restarting daybreak with admin rights");
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
if (processName.IsNullOrWhiteSpace() || File.Exists(processName) is false)
{
throw new InvalidOperationException("Unable to find executable. Aborting restart");
}
var process = new Process()
{
StartInfo = new()
{
Verb = "runas",
WorkingDirectory = Environment.CurrentDirectory,
UseShellExecute = true,
FileName = processName
}
};
if (process.Start() is false)
{
throw new InvalidOperationException($"Unable to start {processName} as admin");
}
Application.Current.Shutdown();
}
private async Task<bool> LaunchGuildwarsProcess(string email, Models.SecureString password, string character)
{
var executable = this.liveOptions.Value.GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (executable is null)
{
throw new ExecutableNotFoundException($"No executable selected");
@@ -129,19 +170,63 @@ namespace Daybreak.Services.ApplicationLauncher
args.Add(character);
}
if (Process.Start(executable.Path, args) is null)
var identity = this.liveOptions.Value.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser ?
System.Security.Principal.WindowsIdentity.GetCurrent().Name :
System.Security.Principal.WindowsIdentity.GetAnonymous().Name;
this.logger.LogInformation($"Launching guildwars as [{identity}] identity");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
Arguments = string.Join(" ", args),
FileName = executable.Path
}
};
if (process.Start() is false)
{
throw new InvalidOperationException($"Unable to launch {executable}");
}
var retries = 0;
while (true)
{
await Task.Delay(100);
retries++;
var gwProcess = Process.GetProcessesByName("gw").FirstOrDefault();
if (gwProcess is null && retries < MaxRetries)
{
continue;
}
else if (gwProcess is null && retries >= MaxRetries)
{
throw new InvalidOperationException("Newly launched gw process not detected");
}
if (gwProcess.MainWindowHandle == IntPtr.Zero)
{
continue;
}
int titleLength = NativeMethods.GetWindowTextLength(gwProcess.MainWindowHandle);
var titleBuffer = new StringBuilder(titleLength);
var readCount = NativeMethods.GetWindowText(gwProcess.MainWindowHandle, titleBuffer, titleLength + 1);
var title = titleBuffer.ToString();
if (title != "Guild Wars")
{
continue;
}
return true;
}
}
private bool GuildwarsProcessDetected()
{
if (this.configurationManager.GetConfiguration().ExperimentalFeatures.MultiLaunchSupport is true)
if (this.liveOptions.Value.ExperimentalFeatures.MultiLaunchSupport is true)
{
try
{
var path = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
var path = this.liveOptions.Value.GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (path is null)
{
return false;
@@ -169,7 +254,7 @@ namespace Daybreak.Services.ApplicationLauncher
private void SetRegistryGuildwarsPath()
{
var path = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
var path = this.liveOptions.Value.GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (path is null)
{
throw new ExecutableNotFoundException("No executable currently selected");
@@ -7,8 +7,9 @@ namespace Daybreak.Services.ApplicationLauncher
bool IsGuildwarsRunning { get; }
bool IsToolboxRunning { get; }
bool IsTexmodRunning { get; }
Task LaunchGuildwars();
Task<bool> LaunchGuildwars();
Task LaunchGuildwarsToolbox();
Task LaunchTexmod();
void RestartDaybreakAsAdmin();
}
}
@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Extensions;
using IServiceProvider = Slim.IServiceProvider;
namespace Daybreak.Services.ApplicationLifetime
{
public sealed class ApplicationLifetimeManager : IApplicationLifetimeManager
{
private readonly IServiceProvider serviceProvider;
private List<Type> RegisteredTypes { get; } = new List<Type>();
public ApplicationLifetimeManager(IServiceProvider serviceProvider)
{
serviceProvider.ThrowIfNull(nameof(serviceProvider));
this.serviceProvider = serviceProvider;
}
public void RegisterService<T>() where T : IApplicationLifetimeService
{
this.RegisteredTypes.Add(typeof(T));
}
public void OnStartup()
{
foreach (var serviceType in this.RegisteredTypes)
{
var service = this.serviceProvider.GetService(serviceType);
service.As<IApplicationLifetimeService>().OnStartup();
}
}
public void OnClosing()
{
foreach (var serviceType in this.RegisteredTypes)
{
var service = this.serviceProvider.GetService(serviceType);
service.As<IApplicationLifetimeService>().OnClosing();
}
}
}
}
@@ -1,8 +0,0 @@
namespace Daybreak.Services.ApplicationLifetime
{
public interface IApplicationLifetimeManager : IApplicationLifetimeProducer
{
void OnStartup();
void OnClosing();
}
}
@@ -1,7 +0,0 @@
namespace Daybreak.Services.ApplicationLifetime
{
public interface IApplicationLifetimeProducer
{
void RegisterService<T>() where T : IApplicationLifetimeService;
}
}
@@ -1,8 +0,0 @@
namespace Daybreak.Services.ApplicationLifetime
{
public interface IApplicationLifetimeService
{
void OnStartup();
void OnClosing();
}
}
+8 -8
View File
@@ -1,10 +1,9 @@
using Daybreak.Services.Bloogum.Models;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using System;
using System.Extensions;
using System.Http;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace Daybreak.Services.Bloogum
@@ -12,15 +11,16 @@ namespace Daybreak.Services.Bloogum
public sealed class BloogumClient : IBloogumClient
{
private const string BaseAddress = "http://bloogum.net/guildwars";
private readonly HttpClient httpClient;
private readonly IHttpClient<BloogumClient> httpClient;
private readonly ILogger logger;
private readonly Random random = new();
public BloogumClient(ILogger logger)
public BloogumClient(
ILogger<BloogumClient> logger,
IHttpClient<BloogumClient> httpClient)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient = new HttpClient();
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
}
public async Task<Optional<Stream>> GetRandomScreenShot()
@@ -53,7 +53,7 @@ namespace Daybreak.Services.Bloogum
}
catch(Exception e)
{
this.logger.LogError(e);
this.logger.LogError(e.ToString());
return Optional.None<Stream>();
}
}
+2 -2
View File
@@ -81,13 +81,13 @@ namespace Daybreak.Services.Bloogum.Models
new Category("stingraystrand", 15),
new Category("fishermenshaven", 4),
new Category("riversideprovince", 31),
new Category("sanctumcay", 21)
new Category("sanctumcay", 21),
new Category("majestysrest", 14)
});
public static readonly Location MaguumaJungle = new(
"maguuma",
new List<Category>
{
new Category("majestysrest", 14),
new Category("druidsoverlook", 1),
new Category("sagelands", 27),
new Category("thewilds", 19),
@@ -1,7 +1,6 @@
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates.Models;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Extensions;
@@ -16,10 +15,10 @@ namespace Daybreak.Services.BuildTemplates
private const string DecodingLookupTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
private readonly static string BuildsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Guild Wars\\Templates\\Skills";
private readonly ILogger logger;
private readonly ILogger<BuildTemplateManager> logger;
public BuildTemplateManager(
ILogger logger)
ILogger<BuildTemplateManager> logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
}
@@ -229,8 +228,10 @@ namespace Daybreak.Services.BuildTemplates
{
var curedTemplate = template.Trim();
var buildMetadata = new BuildMetadata();
buildMetadata.Base64Decoded = template.Select(c => DecodingLookupTable.IndexOf(c)).ToList();
var buildMetadata = new BuildMetadata
{
Base64Decoded = template.Select(c => DecodingLookupTable.IndexOf(c)).ToList(),
};
buildMetadata.BinaryDecoded = buildMetadata.Base64Decoded.Select(b => ToBitString(b)).ToList();
var stream = new DecodeCharStream(buildMetadata.BinaryDecoded.ToArray());
@@ -1,7 +1,6 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using System;
using System.Extensions;
using System.IO;
@@ -13,9 +12,11 @@ namespace Daybreak.Services.Configuration
private const string ConfigName = "Daybreak.config.json";
private ApplicationConfiguration applicationConfiguration;
private readonly ILogger logger;
private readonly ILogger<ConfigurationManager> logger;
public ConfigurationManager(ILogger logger)
public event EventHandler ConfigurationChanged;
public ConfigurationManager(ILogger<ConfigurationManager> logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
try
@@ -39,6 +40,7 @@ namespace Daybreak.Services.Configuration
{
File.WriteAllText(ConfigName, applicationConfiguration.Serialize());
this.applicationConfiguration = applicationConfiguration;
this.ConfigurationChanged?.Invoke(this, new EventArgs());
}
}
}
@@ -1,9 +1,11 @@
using Daybreak.Configuration;
using System;
namespace Daybreak.Services.Configuration
{
public interface IConfigurationManager
{
event EventHandler ConfigurationChanged;
ApplicationConfiguration GetConfiguration();
void SaveConfiguration(ApplicationConfiguration applicationConfiguration);
}
@@ -1,9 +1,9 @@
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Daybreak.Configuration;
using Daybreak.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Security.Cryptography;
@@ -15,14 +15,14 @@ namespace Daybreak.Services.Credentials
public sealed class CredentialManager : ICredentialManager
{
private static readonly byte[] Entropy = Convert.FromBase64String("R3VpbGR3YXJz");
private readonly ILogger logger;
private readonly IConfigurationManager configurationManager;
private readonly ILogger<CredentialManager> logger;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveOptions;
public CredentialManager(
ILogger logger,
IConfigurationManager configurationManager)
ILogger<CredentialManager> logger,
ILiveUpdateableOptions<ApplicationConfiguration> liveOptions)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.logger = logger.ThrowIfNull(nameof(logger));
}
@@ -54,7 +54,7 @@ namespace Daybreak.Services.Credentials
return Task.Run(() =>
{
this.logger.LogInformation("Retrieving credentials");
var config = this.configurationManager.GetConfiguration();
var config = this.liveOptions.Value;
if (config.ProtectedLoginCredentials is null || config.ProtectedLoginCredentials.Count == 0)
{
this.logger.LogInformation("No credentials found");
@@ -63,9 +63,9 @@ namespace Daybreak.Services.Credentials
return config
.ProtectedLoginCredentials
.Select(UnprotectCredentials)
.Where(CredentialsUnprotected)
.Select(ExtractCredentials)
.Select(this.UnprotectCredentials)
.Where(this.CredentialsUnprotected)
.Select(this.ExtractCredentials)
.ToList();
});
}
@@ -74,13 +74,12 @@ namespace Daybreak.Services.Credentials
return Task.Run(() =>
{
this.logger.LogInformation("Storing credentials");
var config = this.configurationManager.GetConfiguration();
config.ProtectedLoginCredentials = loginCredentials
.Select(ProtectCredentials)
.Where(CredentialsProtected)
.Select(ExtractProtectedCredentials)
this.liveOptions.Value.ProtectedLoginCredentials = loginCredentials
.Select(this.ProtectCredentials)
.Where(this.CredentialsProtected)
.Select(this.ExtractProtectedCredentials)
.ToList();
this.configurationManager.SaveConfiguration(config);
this.liveOptions.UpdateOption();
});
}
@@ -1,12 +1,13 @@
using Daybreak.Models.Builds;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Daybreak.Configuration;
using Daybreak.Models.Builds;
using HtmlAgilityPack;
using Microsoft.Extensions.Logging;
using System;
using System.Configuration;
using System.Extensions;
using System.Http;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
@@ -16,18 +17,48 @@ namespace Daybreak.Services.IconRetrieve
private const string NamePlaceholder = "[SKILLNAME]";
private const string BaseUrl = "https://wiki.guildwars.com";
private const string QueryUrl = $"wiki/File:{NamePlaceholder}.jpg";
private const string IconsDirectoryName = "Icons";
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
private readonly HttpClient httpClient = new();
private readonly ILogger logger;
private readonly IHttpClient<IconRetriever> httpClient;
private readonly ILogger<IconRetriever> logger;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
public IconRetriever(
ILogger logger)
ILogger<IconRetriever> logger,
IHttpClient<IconRetriever> httpClient,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.httpClient.BaseAddress = new Uri(BaseUrl);
if (Directory.Exists(IconsDirectoryName) is false)
{
Directory.CreateDirectory(IconsDirectoryName);
}
}
public async Task<Optional<Stream>> GetIcon(Skill skill)
{
if (this.liveOptions.Value.KeepLocalIconCache)
{
this.logger.LogInformation($"{nameof(IconRetriever)} configured to look first in cache before downloading icons");
var maybeIcon = await this.GetLocalIcon(skill);
if (maybeIcon.ExtractValue() is Stream stream)
{
return stream;
}
}
else
{
this.logger.LogInformation($"{nameof(IconRetriever)} configured to skip local cache. Downloading icon");
}
return await this.DownloadIcon(skill);
}
private async Task<Optional<Stream>> DownloadIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_");
@@ -55,13 +86,43 @@ namespace Daybreak.Services.IconRetrieve
if (response.IsSuccessStatusCode)
{
this.logger.LogInformation("Retrieved latest icon stream");
return new MemoryStream(await iconResponse.Content.ReadAsByteArrayAsync());
var iconData = await iconResponse.Content.ReadAsByteArrayAsync();
if (this.liveOptions.Value.KeepLocalIconCache)
{
await SaveIconLocally(skill, iconData);
}
return new MemoryStream(iconData);
}
this.logger.LogError($"Failed to retrieve icon from {BaseUrl + "/" + url}");
return Optional.None<Stream>();
}
private async Task<Optional<Stream>> GetLocalIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
this.logger.LogInformation("Checking local icon cache");
if (File.Exists(IconsLocation.Replace(NamePlaceholder, curedSkillName)))
{
this.logger.LogInformation("Local icon cache found. Retrieving icon");
return new MemoryStream(await File.ReadAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName)));
}
this.logger.LogWarning("No local icon cache found");
return Optional.None<Stream>();
}
private static async Task SaveIconLocally(Skill skill, byte[] data)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
await File.WriteAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName), data);
}
private static string GetHref(HtmlDocument doc)
{
foreach (var child in doc.DocumentNode.Descendants("a"))
@@ -0,0 +1,11 @@
using Daybreak.Models;
using System;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.KeyboardHook
{
public interface IKeyboardHookService : IApplicationLifetimeService
{
event EventHandler<KeyboardHookEventArgs> KeyboardPressed;
}
}
@@ -0,0 +1,116 @@
using Daybreak.Models;
using Microsoft.Extensions.Logging;
using Pepa.Wpf.Utilities;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Extensions;
using System.Linq;
using System.Runtime.InteropServices;
namespace Daybreak.Services.KeyboardHook
{
// Based on https://gist.github.com/Stasonix
// https://stackoverflow.com/questions/604410/global-keyboard-capture-in-c-sharp-application
public sealed class KeyboardHookService : IKeyboardHookService, IDisposable
{
private readonly ILogger<KeyboardHookService> logger;
private IntPtr windowsHookHandle;
private IntPtr user32LibraryHandle;
private NativeMethods.HookProc hookProc;
public event EventHandler<KeyboardHookEventArgs> KeyboardPressed;
public KeyboardHookService(
ILogger<KeyboardHookService> logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.Setup();
}
public void OnStartup()
{
}
public void OnClosing()
{
this.Dispose();
}
private void Setup()
{
this.windowsHookHandle = IntPtr.Zero;
this.hookProc = this.LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.
this.user32LibraryHandle = NativeMethods.LoadLibrary("User32");
if (this.user32LibraryHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogError($"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
this.windowsHookHandle = NativeMethods.SetWindowsHookEx(NativeMethods.WH_KEYBOARD_LL, this.hookProc, this.user32LibraryHandle, 0);
if (this.windowsHookHandle == IntPtr.Zero)
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogError($"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
}
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{
var handled = false;
var wparamTyped = wParam.ToInt32();
if (Enum.IsDefined(typeof(KeyboardState), wparamTyped))
{
var p = Marshal.PtrToStructure(lParam, typeof(KeyboardInput)).Cast<KeyboardInput>();
var eventArguments = new KeyboardHookEventArgs(wparamTyped.Cast<KeyboardState>(), p);
this.KeyboardPressed?.Invoke(this, eventArguments);
handled = eventArguments.Handled;
}
return handled ? (IntPtr)1 : NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
private void Dispose(bool disposing)
{
if (disposing)
{
// because we can unhook only in the same thread, not in garbage collector thread
if (this.windowsHookHandle != IntPtr.Zero)
{
if (NativeMethods.UnhookWindowsHookEx(this.windowsHookHandle) is false)
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogCritical($"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
this.windowsHookHandle = IntPtr.Zero;
this.hookProc -= LowLevelKeyboardProc;
}
}
if (this.user32LibraryHandle != IntPtr.Zero)
{
if (NativeMethods.FreeLibrary(this.user32LibraryHandle) is false) // reduces reference to library by 1.
{
int errorCode = Marshal.GetLastWin32Error();
this.logger.LogCritical($"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
}
this.user32LibraryHandle = IntPtr.Zero;
}
}
~KeyboardHookService()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
@@ -0,0 +1,8 @@
using System.Windows.Extensions.Services;
namespace Daybreak.Services.KeyboardMacros
{
public interface IMacroService : IApplicationLifetimeService
{
}
}
@@ -0,0 +1,106 @@
using Daybreak.Configuration;
using Daybreak.Models;
using Daybreak.Services.KeyboardHook;
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Daybreak.Services.KeyboardMacros
{
public sealed class MacroService : IMacroService
{
private readonly CancellationTokenSource cancellationTokenSource = new();
private readonly IKeyboardHookService keyboardHookService;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly HashSet<Keys> KeysDown = new();
private bool gameActive;
private IntPtr gwWindowHwnd;
public MacroService(
IKeyboardHookService keyboardHookService,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.keyboardHookService = keyboardHookService.ThrowIfNull(nameof(keyboardHookService));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.keyboardHookService.KeyboardPressed += this.KeyboardHookService_KeyboardPressed;
this.SetupGameActiveChecker();
}
private void SetupGameActiveChecker()
{
TaskExtensions.RunPeriodicAsync(() =>
{
var windowHandle = NativeMethods.GetForegroundWindow();
var windowNameLength = NativeMethods.GetWindowTextLength(windowHandle);
var sb = new StringBuilder(windowNameLength);
_ = NativeMethods.GetWindowText(windowHandle, sb, windowNameLength + 1);
if (sb.ToString() != "Guild Wars")
{
this.gameActive = false;
return;
}
this.gwWindowHwnd = windowHandle;
this.gameActive = true;
},
TimeSpan.Zero,
TimeSpan.FromMilliseconds(33),
this.cancellationTokenSource.Token);
}
private void KeyboardHookService_KeyboardPressed(object sender, KeyboardHookEventArgs e)
{
if (this.gameActive && this.liveOptions.Value.ExperimentalFeatures.CanInterceptKeys)
{
if (e.KeyboardState == KeyboardState.KeyDown)
{
this.KeysDown.Add(e.KeyboardInput.Key);
}
else if (e.KeyboardState == KeyboardState.KeyUp)
{
this.KeysDown.Remove(e.KeyboardInput.Key);
return;
}
e.Handled = this.liveOptions.Value.ExperimentalFeatures.Macros
.Where(keyMacro => MacroContainsKey(keyMacro, e.KeyboardInput.Key))
.Where(this.MacroHit)
.Do(this.HandleMacro)
.Any();
}
}
public void OnClosing()
{
this.cancellationTokenSource.Cancel();
}
public void OnStartup()
{
}
private bool MacroHit(KeyMacro keyMacro)
{
return this.KeysDown.Intersect(keyMacro.Keys).OrderBy(key => key).SequenceEqual(keyMacro.Keys.OrderBy(key => key));
}
private void HandleMacro(KeyMacro keyMacro)
{
// TODO: Propagate key to guildwars executable.
}
private static bool MacroContainsKey(KeyMacro keyMacro, Keys lastKey)
{
return keyMacro.Keys.Contains(lastKey);
}
}
}
@@ -1,143 +0,0 @@
using Daybreak.Exceptions;
using Daybreak.Models;
using Daybreak.Utils;
using System;
using System.Collections.Generic;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Daybreak.Services.Logging
{
public class FlatLoggingDatabase : ILoggingDatabase
{
private const string Path = "logs.db";
private const int bufferSize = 10;
private const char separator = '§';
private readonly string filePath = Path;
private readonly List<string> buffer = new List<string>();
public FlatLoggingDatabase()
{
}
public Task<IEnumerable<Log>> GetLogs()
{
return Task.Run(() =>
{
lock (this.buffer)
{
if (this.buffer.Count > 0)
{
this.WriteBufferToFile();
}
}
return this.GetSerializedLogs().Select(s => s.Deserialize<Log>());
});
}
public Task<IEnumerable<Log>> GetLogsByDate(DateTime startTime, DateTime endTime)
{
return Task.Run(() =>
{
lock (this.buffer)
{
if (this.buffer.Count > 0)
{
this.WriteBufferToFile();
}
}
return this.GetSerializedLogs()
.Select(s => s.Deserialize<Log>())
.Where(l => l.Timestamp > startTime && l.Timestamp < endTime);
});
}
public Task<bool> InsertLog(Log log)
{
return Task.Run(() =>
{
lock (this.buffer)
{
this.buffer.Add(log.Serialize());
if (this.buffer.Count > bufferSize)
{
this.WriteBufferToFile();
}
}
return true;
});
}
public Task<bool> ClearDatabase()
{
return Task.Run(() =>
{
lock (this.buffer)
{
this.buffer.Clear();
}
lock (this.filePath)
{
File.WriteAllText(this.filePath, string.Empty);
}
return true;
});
}
public void OnClosing()
{
this.WriteBufferToFile();
}
public void OnStartup()
{
if (!File.Exists(filePath))
{
try
{
using var fs = File.Create(filePath);
}
catch (Exception ex)
{
throw new FatalException("Could not initialize logging. See inner exception for details.", ex);
}
}
}
private void WriteBufferToFile()
{
lock (this.filePath)
{
File.AppendAllLines(this.filePath, this.buffer.Select(s => s + separator), Encoding.UTF8);
}
this.buffer.Clear();
}
private IEnumerable<string> GetSerializedLogs()
{
lock (this.filePath)
{
var stringBuilder = new StringBuilder();
using var fileStream = File.OpenRead(this.filePath);
using var streamReader = new StreamReader(fileStream, Encoding.UTF8);
string serializedLog = null;
while ((serializedLog = streamReader.ReadUntil(separator.ToString())) != null
&& serializedLog != string.Empty
&& serializedLog != "\r"
&& serializedLog != "\n"
&& serializedLog != "\r\n")
{
yield return serializedLog;
}
}
}
}
}
-11
View File
@@ -1,11 +0,0 @@
using Daybreak.Models;
using System;
namespace Daybreak.Services.Logging
{
public interface ILogger
{
void Log(LogLevel logLevel, Exception exception);
void Log(LogLevel logLevel, string message);
}
}
@@ -1,16 +0,0 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLifetime;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Daybreak.Services.Logging
{
public interface ILoggingDatabase : IApplicationLifetimeService
{
Task<bool> ClearDatabase();
Task<IEnumerable<Log>> GetLogsByDate(DateTime startTime, DateTime endTime);
Task<IEnumerable<Log>> GetLogs();
Task<bool> InsertLog(Log log);
}
}
+15
View File
@@ -0,0 +1,15 @@
using Daybreak.Models;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Logging;
namespace Daybreak.Services.Logging
{
public interface ILogsManager : ILogsWriter
{
IEnumerable<Models.Log> GetLogs(Expression<Func<Models.Log, bool>> filter);
IEnumerable<Models.Log> GetLogs();
int DeleteLogs();
}
}
@@ -0,0 +1,46 @@
using LiteDB;
using System;
using System.Collections.Generic;
using System.Extensions;
using System.Linq.Expressions;
using System.Logging;
namespace Daybreak.Services.Logging
{
public sealed class JsonLogsManager : ILogsManager
{
private readonly ILiteDatabase liteDatabase;
public JsonLogsManager(ILiteDatabase liteDatabase)
{
this.liteDatabase = liteDatabase.ThrowIfNull(nameof(liteDatabase));
}
public IEnumerable<Models.Log> GetLogs(Expression<Func<Models.Log, bool>> filter)
{
return this.liteDatabase.GetCollection<Models.Log>().Find(filter);
}
public IEnumerable<Models.Log> GetLogs()
{
return this.liteDatabase.GetCollection<Models.Log>().FindAll();
}
public void WriteLog(Log log)
{
var dbLog = new Models.Log
{
EventId = log.EventId,
Message = log.Exception is null ? log.Message : $"{log.Message}{Environment.NewLine}{log.Exception}",
Category = log.Category,
LogLevel = log.LogLevel,
LogTime = log.LogTime,
CorrelationVector = log.CorrelationVector
};
this.liteDatabase.GetCollection<Models.Log>().Insert(dbLog);
}
public int DeleteLogs()
{
return this.liteDatabase.GetCollection<Models.Log>().DeleteAll();
}
}
}
-42
View File
@@ -1,42 +0,0 @@
using Daybreak.Models;
using System;
namespace Daybreak.Services.Logging
{
public class Logger : ILogger
{
private readonly ILoggingDatabase loggingDatabase;
public Logger(ILoggingDatabase loggingDatabase)
{
this.loggingDatabase = loggingDatabase;
}
public async void Log(LogLevel logLevel, Exception exception)
{
if (exception is null) throw new ArgumentNullException(nameof(exception));
await this.loggingDatabase.InsertLog(
new Log
{
LogLevel = logLevel,
Message = exception.Message,
StackTrace = exception.StackTrace,
Timestamp = DateTime.Now
}).ConfigureAwait(false);
}
public async void Log(LogLevel logLevel, string message)
{
if (message is null) throw new ArgumentNullException(nameof(message));
await this.loggingDatabase.InsertLog(
new Log
{
LogLevel = logLevel,
Message = message,
StackTrace = Environment.StackTrace,
Timestamp = DateTime.Now
}).ConfigureAwait(false);
}
}
}
+6 -1
View File
@@ -1,4 +1,5 @@
using System.Windows.Controls;
using System;
using System.Windows.Controls;
namespace Daybreak.Services.ViewManagement
{
@@ -11,5 +12,9 @@ namespace Daybreak.Services.ViewManagement
void ShowView<T>(object dataContext)
where T : UserControl;
void ShowView(Type type);
void ShowView(Type type, object dataContext);
}
}
+29 -8
View File
@@ -1,6 +1,9 @@
using Slim;
using Daybreak.Models;
using Microsoft.CorrelationVector;
using Slim;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
namespace Daybreak.Services.ViewManagement
@@ -32,17 +35,35 @@ namespace Daybreak.Services.ViewManagement
public void ShowView<T>() where T : UserControl
{
var view = this.serviceManager.GetService<T>();
this.container.Children.Clear();
this.container.Children.Add(view);
this.ShowViewInner(typeof(T), null);
}
public void ShowView<T>(object dataContext) where T : UserControl
{
var view = this.serviceManager.GetService<T>();
this.container.Children.Clear();
this.container.Children.Add(view);
view.DataContext = dataContext;
this.ShowViewInner(typeof(T), dataContext);
}
public void ShowView(Type type)
{
this.ShowViewInner(type, null);
}
public void ShowView(Type type, object dataContext)
{
this.ShowViewInner(type, dataContext);
}
private void ShowViewInner(Type viewType, object dataContext)
{
var scopedManager = this.serviceManager.CreateScope();
scopedManager.As<IServiceManager>().RegisterScoped<ScopeMetadata, ScopeMetadata>((sp) => new ScopeMetadata(new CorrelationVector()));
Application.Current.Dispatcher.Invoke(() =>
{
var view = scopedManager.GetService(viewType).As<UserControl>();
this.container.Children.Clear();
this.container.Children.Add(view);
view.DataContext = dataContext;
});
}
}
}
@@ -0,0 +1,39 @@
using Daybreak.Configuration;
using Daybreak.Services.Configuration;
using System;
using System.Configuration;
using System.Extensions;
namespace Daybreak.Services.Options
{
public sealed class ApplicationConfigurationOptionsManager : IOptionsManager
{
private readonly IConfigurationManager configurationManager;
public ApplicationConfigurationOptionsManager(IConfigurationManager configurationManager)
{
this.configurationManager = configurationManager;
}
public T GetOptions<T>() where T : class
{
if (typeof(T) == typeof(ApplicationConfiguration))
{
return this.configurationManager.GetConfiguration().Cast<T>();
}
throw new InvalidOperationException($"{nameof(ApplicationConfigurationOptionsManager)} cannot return options of type {typeof(T).Name}");
}
public void UpdateOptions<T>(T value) where T : class
{
if (typeof(T) == typeof(ApplicationConfiguration))
{
this.configurationManager.SaveConfiguration(value.Cast<ApplicationConfiguration>());
return;
}
throw new InvalidOperationException($"{nameof(ApplicationConfigurationOptionsManager)} cannot save options of type {typeof(T).Name}");
}
}
}
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Daybreak.Services.Privilege
{
public interface IPrivilegeManager
{
bool AdminPrivileges { get; }
void RequestAdminPrivileges<TCancelView>(string messageToUser, object dataContextOfCancelView = null)
where TCancelView : UserControl;
}
}
@@ -0,0 +1,33 @@
using Daybreak.Models;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Microsoft.Extensions.Logging;
using System.Extensions;
using System.Security.Principal;
using System.Windows.Controls;
namespace Daybreak.Services.Privilege
{
public sealed class PrivilegeManager : IPrivilegeManager
{
public bool AdminPrivileges => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
private readonly IViewManager viewManager;
private readonly ILogger<PrivilegeManager> logger;
public PrivilegeManager(
IViewManager viewManager,
ILogger<PrivilegeManager> logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
}
public void RequestAdminPrivileges<TCancelView>(string messageToUser, object dataContextOfCancelView = null)
where TCancelView : UserControl
{
this.logger.LogInformation("Requesting admin privileges");
this.viewManager.ShowView<RequestElevationView>(new ElevationRequest { View = typeof(TCancelView), DataContext = dataContextOfCancelView, MessageToUser = messageToUser });
}
}
}
@@ -1,9 +0,0 @@
namespace Daybreak.Services.Runtime
{
public interface IRuntimeStore
{
void StoreValue<T>(string name, T value);
bool TryGetValue<T>(string name, out T value);
T GetValue<T>(string name);
}
}
-36
View File
@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Extensions;
namespace Daybreak.Services.Runtime
{
public sealed class RuntimeStore : IRuntimeStore
{
private Dictionary<string, object> InnerStore { get; } = new Dictionary<string, object>();
public T GetValue<T>(string name)
{
if(this.InnerStore.TryGetValue(name, out var value))
{
return value.Cast<T>();
}
throw new InvalidOperationException($"Could not find any value stored with name {name}");
}
public void StoreValue<T>(string name, T value)
{
this.InnerStore[name] = value;
}
public bool TryGetValue<T>(string name, out T value)
{
if (this.InnerStore.TryGetValue(name, out var valueObj))
{
value = valueObj.Cast<T>();
return true;
}
value = default;
return false;
}
}
}
@@ -0,0 +1,11 @@
using Daybreak.Models;
using System.Collections.Generic;
namespace Daybreak.Services.Screens
{
public interface IScreenManager
{
IEnumerable<Screen> Screens { get; }
void MoveGuildwarsToScreen(Screen screen);
}
}
@@ -0,0 +1,38 @@
using Daybreak.Models;
using Microsoft.Extensions.Logging;
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Extensions;
using System.Linq;
namespace Daybreak.Services.Screens
{
public sealed class ScreenManager : IScreenManager
{
private readonly ILogger<ScreenManager> logger;
public IEnumerable<Screen> Screens { get; } = WpfScreenHelper.Screen.AllScreens
.Select((screen, index) => new Screen { Id = index, Size = screen.Bounds });
public ScreenManager(
ILogger<ScreenManager> logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
}
public void MoveGuildwarsToScreen(Screen screen)
{
this.logger.LogInformation($"Attempting to move guildwars to screen {screen.Id}");
var hwnd = GetMainWindowHandle();
NativeMethods.SetWindowPos(hwnd, NativeMethods.HWND_TOP, screen.Size.Left.ToInt(), screen.Size.Top.ToInt(), screen.Size.Width.ToInt(), screen.Size.Height.ToInt(), NativeMethods.SWP_SHOWWINDOW);
}
private static IntPtr GetMainWindowHandle()
{
var process = Process.GetProcessesByName("gw").FirstOrDefault();
return process is not null ? process.MainWindowHandle : throw new InvalidOperationException("Could not find guildwars process");
}
}
}
@@ -1,5 +1,5 @@
using Daybreak.Services.ApplicationLifetime;
using System.Extensions;
using System.Extensions;
using System.Windows.Extensions.Services;
using System.Windows.Media;
namespace Daybreak.Services.Screenshots
@@ -1,5 +1,4 @@
using Daybreak.Services.Logging;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Extensions;
@@ -14,11 +13,11 @@ namespace Daybreak.Services.Screenshots
{
private const string ScreenshotsFolder = "Screenshots";
private readonly List<string> Screenshots = new List<string>();
private readonly ILogger logger;
private readonly List<string> Screenshots = new();
private readonly ILogger<ScreenshotProvider> logger;
private int innerCount = 0;
public ScreenshotProvider(ILogger logger)
public ScreenshotProvider(ILogger<ScreenshotProvider> logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
if (Directory.Exists(ScreenshotsFolder) is false)
@@ -0,0 +1,9 @@
using System.Windows.Extensions.Services;
namespace Daybreak.Services.Shortcuts
{
public interface IShortcutManager : IApplicationLifetimeService
{
bool ShortcutEnabled { get; set; }
}
}
@@ -0,0 +1,115 @@
using Daybreak.Configuration;
using Daybreak.Services.Configuration;
using ShellLink;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.IO;
namespace Daybreak.Services.Shortcuts
{
//TODO: Fix dependency on IConfigurationManager
public sealed class ShortcutManager : IShortcutManager
{
private const string ShortcutName = "Daybreak.lnk";
private readonly IConfigurationManager configurationManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
public bool ShortcutEnabled {
get => this.ShortcutExists();
set
{
if (value is true)
{
this.CreateShortcut();
}
else
{
this.RemoveShortcut();
}
}
}
public ShortcutManager(
IConfigurationManager configurationManager,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.configurationManager.ConfigurationChanged += (_, _) => this.LoadConfiguration();
this.LoadConfiguration();
}
private void LoadConfiguration()
{
var shortcutEnabled = this.liveOptions.Value.PlaceShortcut;
if (shortcutEnabled && this.ShortcutEnabled is false)
{
this.ShortcutEnabled = true;
}
else if (shortcutEnabled is false && this.ShortcutEnabled is true)
{
this.ShortcutEnabled = false;
}
}
private bool ShortcutExists()
{
var shortcutFolder = this.liveOptions.Value.ShortcutLocation;
var shortcutPath = $"{shortcutFolder}\\{ShortcutName}";
if (File.Exists(shortcutPath))
{
var shortcut = Shortcut.ReadFromFile(shortcutPath);
var currentExecutable = Process.GetCurrentProcess()?.MainModule?.FileName;
if (shortcut.ExtraData?.EnvironmentVariableDataBlock?.TargetAnsi?.Equals(currentExecutable) is true)
{
return true;
}
return false;
}
return false;
}
private void CreateShortcut()
{
if (this.ShortcutExists())
{
return;
}
var shortcutFolder = this.liveOptions.Value.ShortcutLocation;
var shortcutPath = $"{shortcutFolder}\\{ShortcutName}";
var currentExecutable = Process.GetCurrentProcess()?.MainModule?.FileName;
var shortcut = Shortcut.CreateShortcut(currentExecutable);
shortcut.StringData = new ShellLink.Structures.StringData
{
WorkingDir = Path.GetDirectoryName(currentExecutable),
RelativePath = "Daybreak.exe"
};
shortcut.WriteToFile(shortcutPath);
}
private void RemoveShortcut()
{
if (this.ShortcutExists() is false)
{
return;
}
var shortcutFolder = this.liveOptions.Value.ShortcutLocation;
var shortcutPath = $"{shortcutFolder}\\{ShortcutName}";
File.Delete(shortcutPath);
}
public void OnStartup()
{
}
public void OnClosing()
{
}
}
}
+98 -39
View File
@@ -1,14 +1,18 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Services.Runtime;
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Models;
using Daybreak.Models.Github;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Daybreak.Views;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Extensions;
using System.Http;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -16,12 +20,13 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Version = Daybreak.Models.Versioning.Version;
namespace Daybreak.Services.Updater
{
public sealed class ApplicationUpdater : IApplicationUpdater
{
private const string UpdateDesiredKey = "UpdateDesired";
private const string LaunchActionName = "Launch_Daybreak";
private const string ExecutionPolicyKey = "ExecutionPolicy";
private const string UpdatedKey = "Updating";
private const string RegistryKey = "Daybreak";
@@ -32,57 +37,75 @@ namespace Daybreak.Services.Updater
private const string OutputPathTag = "{OUTPUTPATH}";
private const string ExecutionPolicyTag = "{EXECUTIONPOLICY}";
private const string ProcessIdTag = "{PROCESSID}";
private const string ExecutableNameTag = "{EXECUTABLE}";
private const string WorkingDirectoryTag = "{WORKINGDIRECTORY}";
private const string RefTagPrefix = "/refs/tags";
private const string VersionListUrl = "https://api.github.com/repos/AlexMacocian/Daybreak/git/refs/tags";
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 DownloadUrl = $"https://github.com/AlexMacocian/Daybreak/releases/download/{VersionTag}/Daybreak{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 PrepareScheduledAction = $"$action = New-ScheduledTaskAction -Execute {ExecutableNameTag} -WorkingDirectory {WorkingDirectoryTag}";
private const string PrepareTriggerForAction = $"$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date)";
private const string RegisterScheduledAction = $"Register-ScheduledTask -Action $action -Trigger $trigger -TaskName {LaunchActionName} | Out-Null";
private const string LaunchScheduledAction = $"Start-ScheduledTask -TaskName {LaunchActionName}";
private const string SleepOneSecond = $"Start-Sleep -s 1";
private const string UnregisterScheduledAction = $"Unregister-ScheduledTask -TaskName {LaunchActionName} -Confirm:$false";
private const string RemoveTempFile = $"Remove-item {TempFile}";
private const string RemovePs1 = $"Remove-item {ExtractAndRunPs1}";
private readonly CancellationTokenSource updateCancellationTokenSource = new();
private readonly ILogger logger;
private readonly ILogger<ApplicationUpdater> logger;
private readonly IViewManager viewManager;
private readonly IRuntimeStore runtimeStore;
private readonly HttpClient httpClient = new();
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly IHttpClient<ApplicationUpdater> httpClient;
public string CurrentVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
public Version CurrentVersion { get; }
public ApplicationUpdater(
ILogger logger,
IRuntimeStore runtimeStore,
IViewManager viewManager)
ILogger<ApplicationUpdater> logger,
ILiveOptions<ApplicationConfiguration> liveOptions,
IViewManager viewManager,
IHttpClient<ApplicationUpdater> httpClient)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.runtimeStore = runtimeStore.ThrowIfNull(nameof(runtimeStore));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
this.httpClient.DefaultRequestHeaders.Add("user-agent", "Daybreak Client");
if (Version.TryParse(Assembly.GetExecutingAssembly().GetName().Version.ToString(), out var currentVersion))
{
if (currentVersion.HasPrefix is false)
{
currentVersion = Version.Parse("v" + currentVersion);
}
this.CurrentVersion = currentVersion;
}
else
{
throw new FatalException($"Application version is invalid: {Assembly.GetExecutingAssembly().GetName().Version}");
}
}
public async Task<bool> DownloadUpdate(UpdateStatus updateStatus)
public async Task<bool> DownloadUpdate(Version version, UpdateStatus updateStatus)
{
updateStatus.CurrentStep = UpdateStatus.CheckingLatestVersion;
var latestVersion = (await this.GetLatestVersion()).ExtractValue();
if (latestVersion is null)
updateStatus.CurrentStep = UpdateStatus.InitializingDownload;
var uri = DownloadUrl.Replace(VersionTag, version.ToString());
using var response = await this.httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode is false)
{
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");
updateStatus.CurrentStep = UpdateStatus.FailedDownload;
this.logger.LogError($"Failed to download update. Details: {await response.Content.ReadAsStringAsync()}");
return false;
}
using var downloadStream = await this.httpClient.GetStreamAsync(uri);
this.logger.LogInformation("Beginning update download");
var downloadStream = await downloadLatestResponse.Content.ReadAsStreamAsync();
var fileStream = File.OpenWrite(TempFile);
var downloadSize = (double)downloadStream.Length;
var downloadSize = (double)response.Content.Headers.ContentLength;
var buffer = new byte[1024];
var length = 0;
double downloaded = 0;
@@ -105,12 +128,29 @@ namespace Daybreak.Services.Updater
return true;
}
public async Task<bool> DownloadLatestUpdate(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;
}
if (Version.TryParse(latestVersion, out var parsedVersion) is false)
{
throw new InvalidOperationException($"Could not parse retrieved version: {latestVersion}");
}
return await this.DownloadUpdate(parsedVersion, updateStatus);
}
public async Task<bool> UpdateAvailable()
{
var version = string.Join('.', this.CurrentVersion.Split('.'));
var maybeLatestVersion = await this.GetLatestVersion();
return maybeLatestVersion.Switch(
onSome: latestVersion => string.Compare(version, latestVersion, true) < 0,
onSome: latestVersion => string.Compare(this.CurrentVersion.ToString().Trim('v'), latestVersion, true) < 0,
onNone: () =>
{
this.logger.LogWarning("Failed to retrieve latest version");
@@ -118,11 +158,25 @@ namespace Daybreak.Services.Updater
}).ExtractValue();
}
public async Task<IEnumerable<Version>> GetVersions()
{
this.logger.LogInformation($"Retrieving version list from {VersionListUrl}");
var response = await this.httpClient.GetAsync(VersionListUrl);
if (response.IsSuccessStatusCode)
{
var serializedList = await response.Content.ReadAsStringAsync();
var versionList = serializedList.Deserialize<GithubRefTag[]>();
return versionList.Select(v => v.Ref.Remove(0, RefTagPrefix.Length)).Select(v => new Version(v));
}
return new List<Version>();
}
public void PeriodicallyCheckForUpdates()
{
System.Extensions.TaskExtensions.RunPeriodicAsync(async () =>
{
if (this.runtimeStore.TryGetValue<bool>(UpdateDesiredKey, out var desiringUpdate) && desiringUpdate is false)
if (this.liveOptions.Value.AutoCheckUpdate is false)
{
this.updateCancellationTokenSource.Cancel();
return;
@@ -251,8 +305,15 @@ namespace Daybreak.Services.Updater
.Replace(InputFileTag, Path.GetFullPath(TempFile))
.Replace(OutputPathTag, Directory.GetCurrentDirectory()),
RemoveTempFile,
PrepareScheduledAction
.Replace(ExecutableNameTag, Process.GetCurrentProcess()?.MainModule?.FileName)
.Replace(WorkingDirectoryTag, Directory.GetCurrentDirectory()),
PrepareTriggerForAction,
RegisterScheduledAction,
LaunchScheduledAction,
SleepOneSecond,
UnregisterScheduledAction,
RemovePs1,
RunClientCommand
});
var process = new Process()
{
@@ -260,12 +321,10 @@ namespace Daybreak.Services.Updater
{
FileName = "powershell.exe",
Arguments = $@"{Directory.GetCurrentDirectory()}\{ExtractAndRunPs1}",
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Maximized,
WorkingDirectory = Directory.GetCurrentDirectory()
WorkingDirectory = Directory.GetCurrentDirectory(),
Verb = "runas"
},
};
this.logger.LogInformation("Created extractor script. Attempting to launch powershell");
@@ -1,15 +1,19 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Models.Versioning;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.Updater
{
public interface IApplicationUpdater : IApplicationLifetimeService
{
string CurrentVersion { get; }
Version CurrentVersion { get; }
void FinalizeUpdate();
void PeriodicallyCheckForUpdates();
Task<IEnumerable<Version>> GetVersions();
Task<bool> UpdateAvailable();
Task<bool> DownloadUpdate(UpdateStatus updateStatus);
Task<bool> DownloadUpdate(Version version, UpdateStatus updateStatus);
Task<bool> DownloadLatestUpdate(UpdateStatus updateStatus);
}
}
-65
View File
@@ -1,65 +0,0 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using System;
namespace Daybreak.Utils
{
public static class LoggingExtensions
{
public static void LogInformation(this ILogger logger, string message)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Information, message);
}
public static void LogError(this ILogger logger, string message)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Error, message);
}
public static void LogCritical(this ILogger logger, string message)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Critical, message);
}
public static void LogWarning(this ILogger logger, string message)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Warning, message);
}
public static void LogInformation(this ILogger logger, Exception e)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Information, e);
}
public static void LogWarning(this ILogger logger, Exception e)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Warning, e);
}
public static void LogError(this ILogger logger, Exception e)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Error, e);
}
public static void LogCritical(this ILogger logger, Exception e)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
logger.Log(LogLevel.Critical, e);
}
}
}
+30 -2
View File
@@ -7,6 +7,14 @@ namespace Pepa.Wpf.Utilities
{
static class NativeMethods
{
public static uint WM_KEYDOWN = 0x0100;
public static uint SWP_SHOWWINDOW = 0x0040;
public static IntPtr HWND_TOPMOST = new(-1);
public static IntPtr HWND_TOP = IntPtr.Zero;
public const int WH_KEYBOARD_LL = 13;
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SystemHandleInformation
{
@@ -89,7 +97,7 @@ namespace Pepa.Wpf.Utilities
public const int WM_SYSCOMMAND = 0x112;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr hObject);
@@ -103,7 +111,27 @@ namespace Pepa.Wpf.Utilities
public static extern NtStatus NtQueryObject(IntPtr ObjectHandle, ObjectInformationClass ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, out int ReturnLength);
[DllImport("ntdll.dll")]
public static extern NtStatus NtQuerySystemInformation(SystemInformationClass SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, out int ReturnLength);
[DllImport("kernel32.dll")]
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern bool QueryFullProcessImageName(IntPtr hProcess, uint dwFlags, StringBuilder lpExeName, ref uint lpdwSize);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hwnd, IntPtr insertAfter, int x, int y, int cx, int cy, uint flags);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hwnd, int cmd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool FreeLibrary(IntPtr hModule);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hHook);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
}
-25
View File
@@ -1,35 +1,10 @@
using Newtonsoft.Json;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
namespace Daybreak.Utils
{
public static class SerializationExtensions
{
public static byte[] SerializeBytes(this object obj)
{
byte[] returnArray;
using (var memoryStream = new MemoryStream())
{
var bf = new BinaryFormatter();
bf.Serialize(memoryStream, obj);
returnArray = memoryStream.ToArray();
}
return returnArray;
}
public static T DeserializeBytes<T>(this byte[] serializedObject)
{
T obj;
using (var memoryStream = new MemoryStream(serializedObject))
{
var bf = new BinaryFormatter();
obj = (T)bf.Deserialize(memoryStream);
}
return obj;
}
public static string Serialize<T>(this T obj)
{
return JsonConvert.SerializeObject(obj);
+14 -8
View File
@@ -9,13 +9,19 @@
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>
<StackPanel VerticalAlignment="Center" Orientation="Vertical">
<TextBlock Text="An update has been detected. Do you want to download the update?" HorizontalAlignment="Center"
FontSize="16" Foreground="Black" Margin="10, 0, 10, 10" TextWrapping="Wrap"></TextBlock>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls:OpaqueButton Text="Yes" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="50" Height="25"
Clicked="YesButton_Clicked" Foreground="Black" FontSize="16"></controls:OpaqueButton>
<controls:OpaqueButton Text="No" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="50" Height="25"
Clicked="NoButton_Clicked" Foreground="Black" Grid.Column="1" FontSize="16"></controls:OpaqueButton>
</Grid>
</StackPanel>
</Grid>
</UserControl>
+37 -14
View File
@@ -1,9 +1,11 @@
using Daybreak.Services.Logging;
using Daybreak.Services.Runtime;
using Daybreak.Configuration;
using Daybreak.Services.Privilege;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
namespace Daybreak.Views
@@ -13,35 +15,56 @@ namespace Daybreak.Views
/// </summary>
public partial class AskUpdateView : UserControl
{
private const string UpdateDesiredKey = "UpdateDesired";
private readonly ILogger logger;
private readonly ILogger<AskUpdateView> logger;
private readonly IViewManager viewManager;
private readonly IRuntimeStore runtimeStore;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveOptions;
private readonly IPrivilegeManager privilegeManager;
private readonly IApplicationUpdater applicationUpdater;
public AskUpdateView(
ILogger logger,
ILogger<AskUpdateView> logger,
IViewManager viewManager,
IRuntimeStore runtimeStore)
ILiveUpdateableOptions<ApplicationConfiguration> liveOptions,
IPrivilegeManager privilegeManager,
IApplicationUpdater applicationUpdater)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.runtimeStore = runtimeStore.ThrowIfNull(nameof(runtimeStore));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.InitializeComponent();
}
private bool CheckIfAdmin()
{
if (this.privilegeManager.AdminPrivileges is false)
{
this.privilegeManager.RequestAdminPrivileges<MainView>("Application needs to be in administrator mode in order to update.");
return false;
}
return true;
}
private void NoButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User declined update");
this.runtimeStore.StoreValue(UpdateDesiredKey, false);
this.liveOptions.Value.AutoCheckUpdate = false;
this.liveOptions.UpdateOption();
this.viewManager.ShowView<MainView>();
}
private void YesButton_Clicked(object sender, System.EventArgs e)
private async void YesButton_Clicked(object sender, System.EventArgs e)
{
this.logger.LogInformation("User accepted update");
this.runtimeStore.StoreValue(UpdateDesiredKey, true);
this.viewManager.ShowView<UpdateView>();
if (this.CheckIfAdmin() is false)
{
return;
}
var latestVersion = (await this.applicationUpdater.GetVersions()).Last();
this.viewManager.ShowView<UpdateView>(latestVersion);
}
}
}
+12 -1
View File
@@ -11,6 +11,7 @@
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
@@ -35,7 +36,17 @@
<controls:SaveButton Foreground="White" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5"
Clicked="SaveButton_Clicked" Grid.Column="2" IsEnabled="{Binding ElementName=_this, Path=SaveButtonEnabled, Mode=OneWay}"></controls:SaveButton>
</Grid>
<controls:BuildTemplate Grid.Row="1" DataContext="{Binding ElementName=_this, Path=CurrentBuild, Mode=OneWay}">
<Grid Grid.Row="1" Margin="10, 0, 10, 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Code: " Foreground="White" Background="Transparent" FontSize="16"></TextBlock>
<TextBox Grid.Column="1" Foreground="White" Background="Transparent" FontSize="16"
Text="{Binding ElementName=_this, Path=CurrentBuildCode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</Grid>
<controls:BuildTemplate x:Name="BuildTemplate" Grid.Row="2" DataContext="{Binding ElementName=_this, Path=CurrentBuild, Mode=OneWay}"
BuildChanged="BuildTemplate_BuildChanged">
</controls:BuildTemplate>
</Grid>
</UserControl>
+65 -19
View File
@@ -1,7 +1,12 @@
using Daybreak.Models.Builds;
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Models.Builds;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using System;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows;
@@ -17,47 +22,89 @@ namespace Daybreak.Views
{
private const string DisallowedChars = "\r\n\\/.";
public readonly static DependencyProperty CurrentBuildProperty =
DependencyPropertyExtensions.Register<BuildTemplateView, BuildEntry>(nameof(CurrentBuild));
public readonly static DependencyProperty SaveButtonEnabledProperty =
DependencyPropertyExtensions.Register<BuildTemplateView, bool>(nameof(SaveButtonEnabled), new PropertyMetadata(false));
private bool supressDecode = false;
private readonly IViewManager viewManager;
private readonly IBuildTemplateManager buildTemplateManager;
private readonly ILogger<BuildTemplateView> logger;
public bool SaveButtonEnabled
{
get => this.GetTypedValue<bool>(SaveButtonEnabledProperty);
set => this.SetValue(SaveButtonEnabledProperty, value);
}
public BuildEntry CurrentBuild
{
get => this.GetTypedValue<BuildEntry>(CurrentBuildProperty);
set => this.SetValue(CurrentBuildProperty, value);
}
[GenerateDependencyProperty(InitialValue = false)]
private bool saveButtonEnabled;
[GenerateDependencyProperty]
private BuildEntry currentBuild;
[GenerateDependencyProperty]
private string currentBuildCode;
public BuildTemplateView(
IViewManager viewManager,
IBuildTemplateManager buildTemplateManager)
IBuildTemplateManager buildTemplateManager,
IIconRetriever iconRetriever,
ILiveOptions<ApplicationConfiguration> liveOptions,
ILogger<ChromiumBrowserWrapper> chromiumLogger,
ILogger<BuildTemplateView> logger)
{
this.buildTemplateManager = buildTemplateManager.ThrowIfNull(nameof(buildTemplateManager));
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.BuildTemplate.InitializeTemplate(iconRetriever, liveOptions, buildTemplateManager, chromiumLogger);
this.DataContextChanged += (sender, contextArgs) =>
{
if (contextArgs.NewValue is BuildEntry)
{
this.logger.LogInformation("Received data context. Setting current build");
this.CurrentBuild = contextArgs.NewValue.As<BuildEntry>();
this.CurrentBuildCode = this.buildTemplateManager.EncodeTemplate(this.CurrentBuild.Build);
}
};
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == CurrentBuildCodeProperty && this.supressDecode is false)
{
this.logger.LogInformation($"Attempting to decode provided template {this.CurrentBuildCode}");
try
{
this.CurrentBuild = new BuildEntry
{
Name = this.CurrentBuild.Name,
PreviousName = this.CurrentBuild.PreviousName,
Build = this.buildTemplateManager.DecodeTemplate(this.CurrentBuildCode)
};
this.logger.LogInformation($"Template {CurrentBuildCode} decoded");
}
catch
{
this.logger.LogWarning($"Failed to decode {this.CurrentBuildCode}. Reverting to default build");
this.CurrentBuild = new BuildEntry
{
Name = this.CurrentBuild.Name,
PreviousName = this.CurrentBuild.PreviousName,
Build = new Build()
};
}
}
}
private void BuildTemplate_BuildChanged(object sender, EventArgs e)
{
try
{
this.supressDecode = true;
this.CurrentBuildCode = this.buildTemplateManager.EncodeTemplate(this.CurrentBuild.Build);
}
finally
{
this.supressDecode = false;
}
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<BuildsListView>();
}
private void SaveButton_Clicked(object sender, EventArgs e)
{
this.buildTemplateManager.SaveBuild(this.CurrentBuild);
@@ -71,7 +118,6 @@ namespace Daybreak.Views
e.Handled = true;
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrWhiteSpace(sender.As<TextBox>().Text))
+10 -10
View File
@@ -1,10 +1,10 @@
using Daybreak.Controls;
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using Microsoft.Win32;
using System;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
@@ -17,15 +17,15 @@ namespace Daybreak.Views
/// </summary>
public partial class ExecutablesView : UserControl
{
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions;
private readonly IViewManager viewManager;
public ObservableCollection<GuildwarsPath> Paths { get; } = new();
public ExecutablesView(
IConfigurationManager configurationManager,
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions,
IViewManager viewManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull(nameof(liveUpdateableOptions));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.GetPaths();
@@ -33,7 +33,7 @@ namespace Daybreak.Views
private void GetPaths()
{
this.Paths.AddRange(this.configurationManager.GetConfiguration().GuildwarsPaths);
this.Paths.AddRange(this.liveUpdateableOptions.Value.GuildwarsPaths);
}
private void BackButton_Clicked(object sender, EventArgs e)
@@ -53,9 +53,8 @@ namespace Daybreak.Views
private void SaveButton_Clicked(object sender, EventArgs e)
{
var config = this.configurationManager.GetConfiguration();
config.GuildwarsPaths = this.Paths.ToList();
this.configurationManager.SaveConfiguration(config);
this.liveUpdateableOptions.Value.GuildwarsPaths = this.Paths.ToList();
this.liveUpdateableOptions.UpdateOption();
this.viewManager.ShowView<SettingsCategoryView>();
}
@@ -81,6 +80,7 @@ namespace Daybreak.Views
{
path.Default = false;
}
gwPath.Default = true;
var view = CollectionViewSource.GetDefaultView(this.Paths);
view.Refresh();
+7 -4
View File
@@ -87,10 +87,11 @@
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Multi-launch support" Foreground="White" FontSize="22" VerticalAlignment="Center" Margin="0, 0, 15, 0" Height="30"></TextBlock>
<TextBlock Text="GWToolbox startup delay (in ms)" Foreground="White" FontSize="22" VerticalAlignment="Center" Margin="0, 0, 15, 0" Height="30"></TextBlock>
<TextBlock Text="Detect build templates (in browser)" Foreground="White" FontSize="22" VerticalAlignment="Center" Margin="0, 0, 15, 0" Height="30"></TextBlock>
<StackPanel Margin="0, 0, 15, 0">
<TextBlock Text="Multi-launch support" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="GWToolbox startup delay (in ms)" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Detect build templates (in browser)" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Launch gw as current user" Foreground="White" FontSize="22" Height="30"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1">
<ToggleButton IsChecked="{Binding ElementName=_this, Path=MultiLaunch, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
@@ -100,6 +101,8 @@
CommandManager.PreviewCanExecute="TextBox_DisallowPaste"></TextBox>
<ToggleButton IsChecked="{Binding ElementName=_this, Path=DynamicBuildLoading, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
<ToggleButton IsChecked="{Binding ElementName=_this, Path=LaunchAsCurrentUser, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
</StackPanel>
</Grid>
</Grid>
@@ -1,6 +1,7 @@
using Daybreak.Services.Configuration;
using Daybreak.Configuration;
using Daybreak.Services.ViewManagement;
using System;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Windows;
@@ -21,10 +22,19 @@ namespace Daybreak.Views
DependencyPropertyExtensions.Register<ExperimentalSettingsView, string>(nameof(GWToolboxLaunchDelay));
public static readonly DependencyProperty DynamicBuildLoadingProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(DynamicBuildLoading));
public static readonly DependencyProperty LaunchAsCurrentUserProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(LaunchAsCurrentUser));
public static readonly DependencyProperty MacrosEnabledProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(MacrosEnabled));
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions;
public bool LaunchAsCurrentUser
{
get => this.GetTypedValue<bool>(LaunchAsCurrentUserProperty);
set => this.SetValue(LaunchAsCurrentUserProperty, value);
}
public bool MultiLaunch
{
get => this.GetTypedValue<bool>(MultiLaunchProperty);
@@ -40,36 +50,45 @@ namespace Daybreak.Views
get => this.GetTypedValue<bool>(DynamicBuildLoadingProperty);
set => this.SetValue(DynamicBuildLoadingProperty, value);
}
public bool MacrosEnabled
{
get => this.GetTypedValue<bool>(MacrosEnabledProperty);
set => this.SetValue(MacrosEnabledProperty, value);
}
public ExperimentalSettingsView(
IViewManager viewManager,
IConfigurationManager configurationManager)
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull(nameof(liveUpdateableOptions));
this.InitializeComponent();
this.LoadExperimentalSettings();
}
private void LoadExperimentalSettings()
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveUpdateableOptions.Value;
this.MultiLaunch = config.ExperimentalFeatures.MultiLaunchSupport;
this.GWToolboxLaunchDelay = config.ExperimentalFeatures.ToolboxAutoLaunchDelay.ToString();
this.DynamicBuildLoading = config.ExperimentalFeatures.DynamicBuildLoading;
this.LaunchAsCurrentUser = config.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser;
this.MacrosEnabled = config.ExperimentalFeatures.CanInterceptKeys;
}
private void SaveExperimentalSettings()
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveUpdateableOptions.Value;
config.ExperimentalFeatures.MultiLaunchSupport = this.MultiLaunch;
config.ExperimentalFeatures.DynamicBuildLoading = this.DynamicBuildLoading;
config.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser = this.LaunchAsCurrentUser;
config.ExperimentalFeatures.CanInterceptKeys = this.MacrosEnabled;
if (int.TryParse(this.GWToolboxLaunchDelay, out var gwToolboxLaunchDelay))
{
config.ExperimentalFeatures.ToolboxAutoLaunchDelay = gwToolboxLaunchDelay;
}
this.configurationManager.SaveConfiguration(config);
this.liveUpdateableOptions.UpdateOption();
}
private void SaveButton_Clicked(object sender, EventArgs e)
+73
View File
@@ -0,0 +1,73 @@
<UserControl x:Class="Daybreak.Views.LogsView"
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"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="WrapText">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</UserControl.Resources>
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<controls:BackButton Foreground="White" Height="30" Width="30" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked"></controls:BackButton>
<controls:BinButton Foreground="White" Height="30" Width="30" HorizontalAlignment="Right" Margin="5"
Clicked="BinButton_Clicked"></controls:BinButton>
<controls:RefreshGlyph Foreground="White" Height="30" Width="30" HorizontalAlignment="Right" Margin="0, 5, 45, 5"
Clicked="RefreshGlyph_Clicked"></controls:RefreshGlyph>
<controls:BackButton Foreground="White" Height="30" Width="30" HorizontalAlignment="Right" Margin="0, 5, 85, 5"
Clicked="ExportButton_Clicked">
<controls:BackButton.RenderTransform>
<RotateTransform Angle="270" CenterX="15" CenterY="15"></RotateTransform>
</controls:BackButton.RenderTransform>
</controls:BackButton>
<DataGrid IsReadOnly="True" Background="Transparent" Foreground="White" Grid.Row="1"
ItemsSource="{Binding ElementName=_this, Path=Logs, Mode=OneWay}" HorizontalScrollBarVisibility="Disabled"
AutoGenerateColumns="False" HeadersVisibility="Column" EnableColumnVirtualization="True"
EnableRowVirtualization="True">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Foreground" Value="White"></Setter>
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="BorderBrush" Value="#80808080"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderBrush" Value="#80808080"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn IsReadOnly="True" Header="DateTime" Binding="{Binding LogTime}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="CV" Binding="{Binding CorrelationVector}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="Category" Binding="{Binding Category}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="LogLevel" Binding="{Binding LogLevel}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTextColumn IsReadOnly="True" Header="EventId" Binding="{Binding EventId}" ElementStyle="{StaticResource WrapText}" Width="auto"/>
<DataGridTemplateColumn IsReadOnly="True" Header="Message" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<controls:LogMessageTemplate Message="{Binding Message}" Foreground="White"></controls:LogMessageTemplate>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
+79
View File
@@ -0,0 +1,79 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using System;
using System.Collections.ObjectModel;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Windows.Controls;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for LogsView.xaml
/// </summary>
public partial class LogsView : UserControl
{
private readonly IViewManager viewManager;
private readonly ILogsManager logManager;
private readonly ILogger<LogsView> logger;
public ObservableCollection<Log> Logs { get; } = new ObservableCollection<Log>();
public LogsView(
IViewManager viewManager,
ILogsManager logManager,
ILogger<LogsView> logger)
{
this.logManager = logManager.ThrowIfNull(nameof(logManager));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.logger = logger.ThrowIfNull(nameof(logger));
this.InitializeComponent();
this.UpdateLogs();
}
private void UpdateLogs()
{
this.Logs.ClearAnd().AddRange(this.logManager.GetLogs(l => l.LogLevel < Microsoft.Extensions.Logging.LogLevel.Trace));
}
private async void ExportButton_Clicked(object sender, EventArgs e)
{
this.logger.LogInformation("Exporting logs");
var saveFileDialog = new SaveFileDialog
{
DefaultExt = "json",
Filter = "Json files (*.json)|*.json",
Title = "Export logs",
ValidateNames = true,
CreatePrompt = true
};
if (saveFileDialog.ShowDialog() is true)
{
var fileName = saveFileDialog.FileName;
this.logger.LogInformation($"Exporting to {fileName}");
await File.WriteAllTextAsync(fileName, this.logManager.GetLogs().ToList().Serialize());
}
else
{
this.logger.LogInformation("Exporting canceled");
}
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsCategoryView>();
}
private void BinButton_Clicked(object sender, EventArgs e)
{
this.logManager.DeleteLogs();
this.UpdateLogs();
}
private void RefreshGlyph_Clicked(object sender, EventArgs e)
{
this.UpdateLogs();
}
}
}
+4 -2
View File
@@ -40,7 +40,8 @@
Clicked="LaunchTexmodButton_Clicked" Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="1"
Visibility="{Binding ElementName=_this, Path=ButtonsVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"></controls:OpaqueButton>
<Grid Grid.Column="2" Margin="10" Visibility="{Binding ElementName=_this, Path=BrowsersEnabled, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}">
<controls:ChromiumBrowserWrapper Address="{Binding ElementName=_this, Path=RightBrowserAddress, Mode=OneWay}"
<controls:ChromiumBrowserWrapper x:Name="RightWebBrowser"
Address="{Binding ElementName=_this, Path=RightBrowserAddress, Mode=OneWay}"
Foreground="White" FavoriteUriChanged="RightBrowser_FavoriteUriChanged"
FavoriteAddress="{Binding ElementName=_this, Path=RightBrowserFavoriteAddress, Mode=OneWay}"
MaximizeClicked="RightChromiumBrowserWrapper_MaximizeClicked"
@@ -48,7 +49,8 @@
CanDownloadBuild="True"/>
</Grid>
<Grid Grid.Column="0" Margin="10" Visibility="{Binding ElementName=_this, Path=BrowsersEnabled, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}">
<controls:ChromiumBrowserWrapper Address="{Binding ElementName=_this, Path=LeftBrowserAddress, Mode=OneWay}"
<controls:ChromiumBrowserWrapper x:Name="LeftWebBrowser"
Address="{Binding ElementName=_this, Path=LeftBrowserAddress, Mode=OneWay}"
Foreground="White" FavoriteUriChanged="LeftBrowser_FavoriteUriChanged"
FavoriteAddress="{Binding ElementName=_this, Path=LeftBrowserFavoriteAddress, Mode=OneWay}"
MaximizeClicked="LeftChromiumBrowserWrapper_MaximizeClicked"
+73 -78
View File
@@ -1,10 +1,16 @@
using Daybreak.Exceptions;
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Exceptions;
using Daybreak.Models.Builds;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Configuration;
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.Screens;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using System;
using System.Configuration;
using System.Extensions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -16,97 +22,69 @@ namespace Daybreak.Views
/// <summary>
/// Interaction logic for StartupView.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class MainView : UserControl
{
public static readonly DependencyProperty ButtonsVisibleProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(ButtonsVisible), new PropertyMetadata(true));
public static readonly DependencyProperty LaunchButtonEnabledProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchButtonEnabled));
public static readonly DependencyProperty LaunchToolboxButtonEnabledProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchToolboxButtonEnabled));
public static readonly DependencyProperty LaunchTexmodButtonEnabledProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(LaunchTexmodButtonEnabled));
public static readonly DependencyProperty BrowsersEnabledProperty =
DependencyPropertyExtensions.Register<MainView, bool>(nameof(BrowsersEnabled), new PropertyMetadata(true));
public static readonly DependencyProperty RightBrowserAddressProperty =
DependencyPropertyExtensions.Register<MainView, string>(nameof(RightBrowserAddress));
public static readonly DependencyProperty LeftBrowserAddressProperty =
DependencyPropertyExtensions.Register<MainView, string>(nameof(LeftBrowserAddress));
public static readonly DependencyProperty RightBrowserFavoriteAddressProperty =
DependencyPropertyExtensions.Register<MainView, string>(nameof(RightBrowserFavoriteAddress));
public static readonly DependencyProperty LeftBrowserFavoriteAddressProperty =
DependencyPropertyExtensions.Register<MainView, string>(nameof(LeftBrowserFavoriteAddress));
private readonly IApplicationLauncher applicationDetector;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveOptions;
private readonly IScreenManager screenManager;
private readonly IBuildTemplateManager buildTemplateManager;
private readonly ILogger<ChromiumBrowserWrapper> browserLogger;
private readonly CancellationTokenSource cancellationTokenSource = new();
private bool leftBrowserMaximized = false;
private bool rightBrowserMaximized = false;
public bool ButtonsVisible
{
get => this.GetTypedValue<bool>(ButtonsVisibleProperty);
set => this.SetTypedValue(ButtonsVisibleProperty, value);
}
public string RightBrowserFavoriteAddress
{
get => this.GetTypedValue<string>(RightBrowserFavoriteAddressProperty);
set => this.SetTypedValue(RightBrowserFavoriteAddressProperty, value);
}
public string LeftBrowserFavoriteAddress
{
get => this.GetTypedValue<string>(LeftBrowserFavoriteAddressProperty);
set => this.SetTypedValue(LeftBrowserFavoriteAddressProperty, value);
}
public string RightBrowserAddress
{
get => this.GetTypedValue<string>(RightBrowserAddressProperty);
set => this.SetTypedValue(RightBrowserAddressProperty, value);
}
public string LeftBrowserAddress
{
get => this.GetTypedValue<string>(LeftBrowserAddressProperty);
set => this.SetTypedValue(LeftBrowserAddressProperty, value);
}
public bool LaunchButtonEnabled
{
get => this.GetTypedValue<bool>(LaunchButtonEnabledProperty);
set => this.SetTypedValue(LaunchButtonEnabledProperty, value);
}
public bool LaunchToolboxButtonEnabled
{
get => this.GetTypedValue<bool>(LaunchToolboxButtonEnabledProperty);
set => this.SetTypedValue(LaunchToolboxButtonEnabledProperty, value);
}
public bool LaunchTexmodButtonEnabled
{
get => this.GetTypedValue<bool>(LaunchTexmodButtonEnabledProperty);
set => this.SetTypedValue(LaunchTexmodButtonEnabledProperty, value);
}
public bool BrowsersEnabled
{
get => this.GetTypedValue<bool>(BrowsersEnabledProperty);
set => this.SetTypedValue(BrowsersEnabledProperty, value);
}
[GenerateDependencyProperty(InitialValue = true)]
private bool buttonsVisible;
[GenerateDependencyProperty]
private string rightBrowserFavoriteAddress;
[GenerateDependencyProperty]
private string leftBrowserFavoriteAddress;
[GenerateDependencyProperty]
private string rightBrowserAddress;
[GenerateDependencyProperty]
private string leftBrowserAddress;
[GenerateDependencyProperty]
private bool launchButtonEnabled;
[GenerateDependencyProperty]
private bool launchToolboxButtonEnabled;
[GenerateDependencyProperty]
private bool launchTexmodButtonEnabled;
[GenerateDependencyProperty(InitialValue = true)]
private bool browsersEnabled;
public MainView(
IApplicationLauncher applicationDetector,
IViewManager viewManager,
IConfigurationManager configurationManager)
ILiveUpdateableOptions<ApplicationConfiguration> liveOptions,
IScreenManager screenManager,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> browserLogger)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.browserLogger = browserLogger;
this.buildTemplateManager = buildTemplateManager.ThrowIfNull(nameof(buildTemplateManager));
this.screenManager = screenManager.ThrowIfNull(nameof(screenManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.applicationDetector = applicationDetector.ThrowIfNull(nameof(applicationDetector));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.PeriodicallyCheckGameState();
this.InitializeBrowsers();
this.NavigateToDefaults();
}
private void InitializeBrowsers()
{
this.LeftWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
this.RightWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
}
private void NavigateToDefaults()
{
var applicationConfiguration = this.configurationManager.GetConfiguration();
var applicationConfiguration = this.liveOptions.Value;
if (applicationConfiguration.BrowsersEnabled)
{
this.LeftBrowserFavoriteAddress = applicationConfiguration.LeftBrowserDefault;
@@ -145,10 +123,27 @@ namespace Daybreak.Views
{
try
{
await this.applicationDetector.LaunchGuildwars();
if (this.configurationManager.GetConfiguration().ToolboxAutoLaunch is true)
if (await this.applicationDetector.LaunchGuildwars() is false)
{
var delay = this.configurationManager.GetConfiguration().ExperimentalFeatures.ToolboxAutoLaunchDelay;
return;
}
if (this.liveOptions.Value.SetGuildwarsWindowSizeOnLaunch)
{
var id = this.liveOptions.Value.DesiredGuildwarsScreen;
var desiredScreen = this.screenManager.Screens.Skip(id).FirstOrDefault();
if (desiredScreen is null)
{
throw new InvalidOperationException($"Unable to set guildwars on desired screen. No screen with id {id}");
}
await Task.Delay(1000);
this.screenManager.MoveGuildwarsToScreen(desiredScreen);
}
if (this.liveOptions.Value.ToolboxAutoLaunch is true)
{
var delay = this.liveOptions.Value.ExperimentalFeatures.ToolboxAutoLaunchDelay;
await Task.Delay(delay);
await this.applicationDetector.LaunchGuildwarsToolbox();
}
@@ -187,23 +182,23 @@ namespace Daybreak.Views
}
}
private void ChromiumBrowserWrapper_BuildDecoded(object sender, Models.Builds.Build e)
private void ChromiumBrowserWrapper_BuildDecoded(object sender, Build e)
{
this.viewManager.ShowView<BuildTemplateView>(new BuildEntry { Build = e, Name = string.Empty });
}
private void LeftBrowser_FavoriteUriChanged(object sender, string e)
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveOptions.Value;
config.LeftBrowserDefault = e;
this.configurationManager.SaveConfiguration(config);
this.liveOptions.UpdateOption();
}
private void RightBrowser_FavoriteUriChanged(object sender, string e)
{
var config = this.configurationManager.GetConfiguration();
var config = this.liveOptions.Value;
config.RightBrowserDefault = e;
this.configurationManager.SaveConfiguration(config);
this.liveOptions.UpdateOption();
}
private void LeftChromiumBrowserWrapper_MaximizeClicked(object sender, EventArgs e)
+28
View File
@@ -0,0 +1,28 @@
<UserControl x:Class="Daybreak.Views.RequestElevationView"
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" Background="White" MinHeight="200" MinWidth="400">
<StackPanel VerticalAlignment="Center" Orientation="Vertical">
<TextBlock Text="{Binding MessageToUser}" Margin="10, 0, 10, 0" Foreground="Black"
TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<TextBlock Text="Do you want to restart the application with administrator rights?" HorizontalAlignment="Center"
FontSize="16" Foreground="Black" Margin="10, 0, 10, 10" TextWrapping="Wrap"></TextBlock>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls:OpaqueButton Text="Yes" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="50" Height="25"
Clicked="YesButton_Clicked" Foreground="Black" FontSize="16"></controls:OpaqueButton>
<controls:OpaqueButton Text="No" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="50" Height="25"
Clicked="NoButton_Clicked" Foreground="Black" Grid.Column="1" FontSize="16"></controls:OpaqueButton>
</Grid>
</StackPanel>
</Grid>
</UserControl>

Some files were not shown because too many files have changed in this diff Show More