Compare commits

...
21 Commits
Author SHA1 Message Date
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
Alexandru Macocian 16030063ed Fix skill icon not unloading.
Moved all help icons on same side.
2021-04-23 19:52:09 +02:00
Alexandru Macocian 458696ad8f Fixed a bug where skill templates would try to load the image for No Skill.
Fixed a bug where build template manager would delete saved template.
2021-04-23 17:28:12 +02:00
Alexandru Macocian 6201702563 Skill holder now properly shows the top half of icons in the case where icons are bigger than normal. 2021-04-23 17:14:24 +02:00
Alexandru Macocian c5bea23a8f Make professions and attributes searchable.
Minor QOL changes to build view.
Handle exception caused by browser initialization getting canceled.
2021-04-23 16:59:31 +02:00
Alexandru Macocian e5f38f77ef Reverted html agility pack. 2021-04-23 11:40:05 +02:00
57 changed files with 1678 additions and 249 deletions
@@ -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,9 @@ 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; }
}
}
@@ -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();
}
}
@@ -7,8 +7,11 @@ using Daybreak.Services.Credentials;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
using Daybreak.Services.Screens;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Shortcuts;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
@@ -37,6 +40,9 @@ namespace Daybreak.Configuration
serviceProducer.RegisterSingleton<IRuntimeStore, RuntimeStore>();
serviceProducer.RegisterSingleton<IBuildTemplateManager, BuildTemplateManager>();
serviceProducer.RegisterSingleton<IIconRetriever, IconRetriever>();
serviceProducer.RegisterSingleton<IPrivilegeManager, PrivilegeManager>();
serviceProducer.RegisterSingleton<IScreenManager, ScreenManager>();
serviceProducer.RegisterSingleton<IShortcutManager, ShortcutManager>();
}
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
@@ -45,6 +51,7 @@ namespace Daybreak.Configuration
applicationLifetimeProducer.RegisterService<ILoggingDatabase>();
applicationLifetimeProducer.RegisterService<IScreenshotProvider>();
applicationLifetimeProducer.RegisterService<IApplicationUpdater>();
applicationLifetimeProducer.RegisterService<IShortcutManager>();
}
public static void RegisterViews(IViewProducer viewProducer)
{
@@ -60,6 +67,8 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<ExecutablesView>();
viewProducer.RegisterView<BuildTemplateView>();
viewProducer.RegisterView<BuildsListView>();
viewProducer.RegisterView<RequestElevationView>();
viewProducer.RegisterView<ScreenChoiceView>();
}
}
}
+19
View File
@@ -0,0 +1,19 @@
<UserControl x:Class="Daybreak.Controls.HelpButton"
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>
<Grid>
<Ellipse x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Visibility="Visible" Opacity="0.1" Width="30" Height="30" />
<Ellipse Width="30" Height="30" StrokeThickness="2" Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Ellipse>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Center"
Data="m4.99353,17.899l0,-1.47c0,-1.436 0.322,-2.188 1.075,-3.229l2.404,-3.3c1.254,-1.721 1.684,-2.546 1.684,-3.766c0,-2.044 -1.434,-3.335 -3.479,-3.335c-2.008,0 -3.299,1.219 -3.729,3.407c-0.036,0.215 -0.179,0.323 -0.395,0.287l-2.259,-0.395c-0.216,-0.036 -0.323,-0.179 -0.288,-0.395c0.539,-3.443 3.014,-5.703 6.744,-5.703c3.872,0 6.49,2.546 6.49,6.097c0,1.722 -0.608,2.977 -1.828,4.663l-2.403,3.3c-0.717,0.968 -0.933,1.47 -0.933,2.689l0,1.147c0,0.215 -0.143,0.358 -0.358,0.358l-2.367,0c-0.215,0.004 -0.358,-0.14 -0.358,-0.355zm-0.179,3.444c0,-0.215 0.143,-0.358 0.359,-0.358l2.726,0c0.215,0 0.358,0.144 0.358,0.358l0,3.084c0,0.216 -0.144,0.358 -0.358,0.358l-2.726,0c-0.217,0 -0.359,-0.143 -0.359,-0.358l0,-3.084z"></Path>
<Ellipse Fill="Transparent" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"></Ellipse>
</Grid>
</Viewbox>
</UserControl>
@@ -0,0 +1,37 @@
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for AddButton.xaml
/// </summary>
public partial class HelpButton : UserControl
{
public event EventHandler Clicked;
public HelpButton()
{
InitializeComponent();
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0.6;
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0;
}
private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
this.Clicked?.Invoke(this, e);
}
}
}
}
@@ -4,17 +4,22 @@
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"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="10, 0, 10, 0">
<local:HelpButton Width="20" Height="20" Margin="3, 0, 3, 0" Foreground="White"
Clicked="HelpButton_Clicked" Cursor="Hand"></local:HelpButton>
<TextBlock Text="{Binding Attribute.Name}" FontSize="16" Foreground="White"></TextBlock>
</WrapPanel>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="10, 0, 10, 0">
<local:MinusButton Foreground="White" Height="20" Clicked="MinusButton_Clicked"></local:MinusButton>
<local:MinusButton Foreground="White" Height="20" Clicked="MinusButton_Clicked" Cursor="Hand"
IsEnabled="{Binding ElementName=_this, Path=CanSubtract, Mode=OneWay}"></local:MinusButton>
<TextBox Background="Transparent" Foreground="White" FontSize="16" IsReadOnly="True" Width="30"
Text="{Binding Points}"></TextBox>
<local:AddButton Foreground="White" Height="20" Clicked="AddButton_Clicked"></local:AddButton>
<local:AddButton Foreground="White" Height="20" Clicked="AddButton_Clicked" Cursor="Hand"
IsEnabled="{Binding ElementName=_this, Path=CanAdd, Mode=OneWay}"></local:AddButton>
</WrapPanel>
</Grid>
</UserControl>
@@ -1,6 +1,9 @@
using Daybreak.Models.Builds;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls
{
@@ -9,9 +12,45 @@ namespace Daybreak.Controls
/// </summary>
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 AttributeTemplate()
{
InitializeComponent();
this.InitializeComponent();
this.DataContextChanged += AttributeTemplate_DataContextChanged;
}
private void AttributeTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is AttributeEntry attributeEntry)
{
if (attributeEntry.Points > 0)
{
this.CanSubtract = true;
}
if (attributeEntry.Points < 12)
{
this.CanAdd = true;
}
}
}
private void MinusButton_Clicked(object sender, System.EventArgs e)
@@ -19,6 +58,8 @@ namespace Daybreak.Controls
if (this.DataContext.As<AttributeEntry>().Points > 0)
{
this.DataContext.As<AttributeEntry>().Points--;
this.CanSubtract = this.DataContext.As<AttributeEntry>().Points > 0;
this.CanAdd = true;
}
}
@@ -27,6 +68,16 @@ namespace Daybreak.Controls
if (this.DataContext.As<AttributeEntry>().Points < 12)
{
this.DataContext.As<AttributeEntry>().Points++;
this.CanAdd = this.DataContext.As<AttributeEntry>().Points < 12;
this.CanSubtract = true;
}
}
private void HelpButton_Clicked(object sender, System.EventArgs e)
{
if (this.DataContext is AttributeEntry attributeEntry)
{
this.HelpClicked?.Invoke(this, attributeEntry);
}
}
}
+24 -18
View File
@@ -24,15 +24,16 @@
</Grid.ColumnDefinitions>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Primary Profession: " FontSize="16" Foreground="White"></TextBlock>
<ListView FontSize="16" Foreground="White" Grid.Column="1"
<TextBlock Text="Primary Profession: " FontSize="16" Foreground="White" Grid.Column="1"></TextBlock>
<ListView FontSize="16" Foreground="White" Grid.Column="2" BorderThickness="0"
Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Professions, Mode=OneWay}"
SelectedItem="{Binding ElementName=_this, Path=PrimaryProfession, Mode=TwoWay}" Height="25"
SelectedItem="{Binding ElementName=_this, Path=PrimaryProfession, Mode=TwoWay}" Height="30"
SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Disabled"
PreviewMouseWheel="ListView_DisableMouseWheel">
PreviewMouseWheel="ListView_NavigateWithMouseWheel">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, Mode=OneWay}"></TextBlock>
@@ -42,18 +43,21 @@
<behaviors:ScrollIntoView></behaviors:ScrollIntoView>
</interactivity:Interaction.Behaviors>
</ListView>
<local:HelpButton Grid.Column="0" Foreground="White" Width="20" Height="20" Margin="3"
Clicked="HelpButtonPrimary_Clicked" Cursor="Hand"></local:HelpButton>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Secondary Profession: " FontSize="16" Foreground="White"></TextBlock>
<ListView FontSize="16" Foreground="White" Grid.Column="1"
<TextBlock Text="Secondary Profession: " FontSize="16" Foreground="White" Grid.Column="1"></TextBlock>
<ListView FontSize="16" Foreground="White" Grid.Column="2" BorderThickness="0"
Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Professions, Mode=OneWay}"
SelectedItem="{Binding ElementName=_this, Path=SecondaryProfession, Mode=TwoWay}" Height="25"
SelectedItem="{Binding ElementName=_this, Path=SecondaryProfession, Mode=TwoWay}" Height="30"
SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Disabled"
PreviewMouseWheel="ListView_DisableMouseWheel">
PreviewMouseWheel="ListView_NavigateWithMouseWheel">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, Mode=OneWay}"></TextBlock>
@@ -63,13 +67,15 @@
<behaviors:ScrollIntoView></behaviors:ScrollIntoView>
</interactivity:Interaction.Behaviors>
</ListView>
<local:HelpButton Grid.Column="0" Foreground="White" Width="20" Height="20" Margin="3"
Clicked="HelpButtonSecondary_Clicked" Cursor="Hand"></local:HelpButton>
</Grid>
<Grid Grid.Row="3">
<ListBox Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Attributes, Mode=OneWay}"
HorizontalContentAlignment="Stretch">
HorizontalContentAlignment="Stretch" BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<local:AttributeTemplate></local:AttributeTemplate>
<local:AttributeTemplate HelpClicked="AttributeTemplate_HelpClicked"></local:AttributeTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
@@ -90,42 +96,42 @@
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<local:SkillTemplate Grid.Column="0"
Foreground="White"
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"
Foreground="White"
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"
Foreground="White"
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"
Foreground="White"
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"
Foreground="White"
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"
Foreground="White"
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"
Foreground="White"
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"
Foreground="White"
Foreground="White" Cursor="Hand"
FontSize="22" DataContext="{Binding ElementName=_this, Path=Skill7, Mode=TwoWay}"
VerticalAlignment="Stretch" Clicked="SkillTemplate_Clicked"
RemoveClicked="SkillTemplate_RemoveClicked"></local:SkillTemplate>
@@ -1,6 +1,7 @@
using Daybreak.Launch;
using Daybreak.Models.Builds;
using Daybreak.Services.IconRetrieve;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Extensions;
@@ -20,8 +21,8 @@ namespace Daybreak.Controls
/// </summary>
public partial class BuildTemplate : UserControl
{
private const string SkillNamePlaceholder = "[NAME]";
private const string BaseAddress = $"https://wiki.guildwars.com/wiki/{SkillNamePlaceholder}";
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));
@@ -137,8 +138,8 @@ namespace Daybreak.Controls
private void Grid_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.SkillBrowser.Width = 0;
this.SkillsListView.Width = 0;
this.HideSkillListView();
this.HideInfoBrowser();
}
private void LoadAttributes()
@@ -244,21 +245,79 @@ namespace Daybreak.Controls
this.Skill7 = build.Build.Skills[7];
}
private void BrowseToInfo(string infoName)
{
var address = BaseAddress.Replace(InfoNamePlaceholder, infoName.Replace(" ", "_"));
this.SkillBrowser.Address = address;
this.ShowInfoBrowser();
}
private void ShowInfoBrowser()
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
}
private void HideInfoBrowser()
{
this.SkillBrowser.Width = 0;
}
private void ShowSkillListView()
{
this.SkillBrowser.Width = 0;
this.SkillsListView.Width = 400;
}
private void HideSkillListView()
{
this.SkillsListView.Width = 0;
}
private void HelpButtonPrimary_Clicked(object sender, System.EventArgs e)
{
if (this.PrimaryProfession == Profession.None)
{
return;
}
this.BrowseToInfo(PrimaryProfession.Name);
if (e is RoutedEventArgs routedEventArgs)
{
routedEventArgs.Handled = true;
}
}
private void HelpButtonSecondary_Clicked(object sender, System.EventArgs e)
{
if (this.SecondaryProfession == Profession.None)
{
return;
}
this.BrowseToInfo(this.SecondaryProfession.Name);
if (e is RoutedEventArgs routedEventArgs)
{
routedEventArgs.Handled = true;
}
}
private void AttributeTemplate_HelpClicked(object sender, AttributeEntry e)
{
this.BrowseToInfo(e.Attribute.Name);
}
private void SkillTemplate_Clicked(object sender, RoutedEventArgs e)
{
var skill = sender.As<SkillTemplate>().DataContext.As<Skill>();
if (skill == Skill.NoSkill)
{
this.SkillsListView.Width = 400;
this.SkillBrowser.Width = 0;
this.ShowSkillListView();
this.selectingSkillTemplate = sender.As<SkillTemplate>();
}
else
{
var address = BaseAddress.Replace(SkillNamePlaceholder, skill.Name.Replace(" ", "_"));
this.SkillBrowser.Address = address;
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
this.BrowseToInfo(skill.Name);
}
e.Handled = true;
}
@@ -272,12 +331,12 @@ namespace Daybreak.Controls
{
if (this.selectingSkillTemplate is null)
{
this.SkillsListView.Width = 0;
this.HideSkillListView();
return;
}
this.selectingSkillTemplate.DataContext = sender.As<ListView>().SelectedItem;
this.SkillsListView.Width = 0;
this.HideSkillListView();
this.loadedBuild.Build.Skills[0] = Skill0;
this.loadedBuild.Build.Skills[1] = Skill1;
this.loadedBuild.Build.Skills[2] = Skill2;
@@ -288,9 +347,21 @@ namespace Daybreak.Controls
this.loadedBuild.Build.Skills[7] = Skill7;
}
private void ListView_DisableMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
private void ListView_NavigateWithMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
e.Handled = true;
if (e.Delta > 0)
{
sender.As<ListView>().SelectedIndex = sender.As<ListView>().SelectedIndex > 0 ?
sender.As<ListView>().SelectedIndex - 1 :
0;
}
else
{
sender.As<ListView>().SelectedIndex = sender.As<ListView>().SelectedIndex < sender.As<ListView>().Items.Count - 1 ?
sender.As<ListView>().SelectedIndex + 1 :
sender.As<ListView>().Items.Count;
}
}
}
}
@@ -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,73 @@
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>
public partial class ScreenTemplate : UserControl
{
public static readonly DependencyProperty ScreenIdProperty =
DependencyPropertyExtensions.Register<ScreenTemplate, string>(nameof(ScreenId));
public static readonly DependencyProperty HighlightProperty =
DependencyPropertyExtensions.Register<ScreenTemplate, Brush>(nameof(Highlight));
public event EventHandler<Screen> Clicked;
public string ScreenId
{
get => this.GetTypedValue<string>(ScreenIdProperty);
set => this.SetValue(ScreenIdProperty, value);
}
public Brush Highlight
{
get => this.GetTypedValue<Brush>(HighlightProperty);
set => this.SetValue(HighlightProperty, value);
}
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>());
}
}
}
@@ -17,8 +17,11 @@
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Image VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Source="{Binding ElementName=_this, Path=ImageSource, Mode=OneWay}"></Image>
<Image VerticalAlignment="Top" HorizontalAlignment="Left"
Width="{Binding ElementName=_this, Path=ActualWidth, Mode=OneWay}"
Height="{Binding ElementName=_this, Path=ActualWidth, Mode=OneWay}"
Source="{Binding ElementName=_this, Path=ImageSource, Mode=OneWay}"
Stretch="UniformToFill"></Image>
<Border BorderThickness="5" BorderBrush="Black"></Border>
<Border BorderThickness="5" BorderBrush="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Opacity="{Binding ElementName=_this, Path=BorderOpacity, Mode=OneWay}"></Border>
@@ -48,15 +48,22 @@ namespace Daybreak.Controls
private void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is Skill)
if (e.NewValue is Skill skill)
{
Task.Run(() => GetImageStream(e.NewValue.As<Skill>())).ContinueWith((previousTask) =>
if (skill != Skill.NoSkill)
{
this.Dispatcher.Invoke(() =>
Task.Run(() => GetImageStream(skill)).ContinueWith((previousTask) =>
{
this.ImageSource = GetImageSource(previousTask.Result);
this.Dispatcher.Invoke(() =>
{
this.ImageSource = GetImageSource(previousTask.Result);
});
});
});
}
else if (this.ImageSource is not null)
{
this.ImageSource = null;
}
}
}
+13 -5
View File
@@ -9,22 +9,26 @@
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<Version>0.7.2</Version>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.8.6</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.818.41" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Slim" Version="1.2.1" />
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
<PackageReference Include="Slim" Version="1.2.2" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.1.4" />
<PackageReference Include="WCL" Version="1.0.2" />
<PackageReference Include="WpfExtended" Version="0.2.0" />
<PackageReference Include="WpfExtended" Version="0.2.1" />
<PackageReference Include="WpfScreenHelper" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Controls\Buttons\HelpButton.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Controls\Buttons\MinusButton.xaml.cs">
<SubType>Code</SubType>
</Compile>
@@ -37,6 +41,10 @@
</ItemGroup>
<ItemGroup>
<Page Update="Controls\Buttons\HelpButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Controls\Buttons\MinusButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
+14
View File
@@ -9,6 +9,8 @@ using Microsoft.Web.WebView2.Wpf;
using Slim;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Extensions;
@@ -45,6 +47,18 @@ namespace Daybreak.Launch
MessageBox.Show(fatalException.ToString());
return false;
}
else if (e is AggregateException aggregateException)
{
if (aggregateException.InnerExceptions.FirstOrDefault() is COMException comException &&
comException.Message.Contains("Invalid window handle"))
{
/*
* Ignore exception caused by browser failing to initialize due to missing window.
* Likely caused by switching windows before browser was initialized.
*/
return true;
}
}
MessageBox.Show(e.ToString());
return true;
+49 -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,17 @@
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">
<TextBox Grid.Row="1" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Opacity="0.6"
Text="{Binding ElementName=_this, Path=CurrentVersionText, Mode=OneWay}" IsReadOnly="True" FontSize="10"
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></TextBox>
<TextBox 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}}"></TextBox>
<TextBox 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}}"></TextBox>
</WrapPanel>
<Grid x:Name="Container" Grid.Row="1">
</Grid>
<wcl:Border OnResize="Border_OnResize" Grid.RowSpan="2" Active="True"></wcl:Border>
+13 -1
View File
@@ -1,4 +1,5 @@
using Daybreak.Services.Bloogum;
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
@@ -25,11 +26,13 @@ namespace Daybreak.Launch
{
public static readonly DependencyProperty CreditTextProperty = DependencyPropertyExtensions.Register<MainWindow, string>(nameof(CreditText));
public static readonly DependencyProperty CurrentVersionTextProperty = DependencyPropertyExtensions.Register<MainWindow, string>(nameof(CurrentVersionText));
public static readonly DependencyProperty IsRunningAsAdminProperty = DependencyPropertyExtensions.Register<MainWindow, bool>(nameof(IsRunningAsAdmin));
private readonly IViewManager viewManager;
private readonly IScreenshotProvider screenshotProvider;
private readonly IBloogumClient bloogumClient;
private readonly IApplicationUpdater applicationUpdater;
private readonly IPrivilegeManager privilegeManager;
private readonly CancellationTokenSource cancellationToken = new();
public string CreditText
@@ -44,18 +47,27 @@ namespace Daybreak.Launch
set => this.SetValue(CurrentVersionTextProperty, value);
}
public bool IsRunningAsAdmin
{
get => this.GetTypedValue<bool>(IsRunningAsAdminProperty);
set => this.SetValue(IsRunningAsAdminProperty, value);
}
public MainWindow(
IViewManager viewManager,
IScreenshotProvider screenshotProvider,
IBloogumClient bloogumClient,
IApplicationUpdater applicationUpdater)
IApplicationUpdater applicationUpdater,
IPrivilegeManager privilegeManager)
{
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.InitializeComponent();
this.CurrentVersionText = this.applicationUpdater.CurrentVersion;
this.IsRunningAsAdmin = this.privilegeManager.AdminPrivileges;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
+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; }
}
}
+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
}
}
+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,7 +3,9 @@ using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Privilege;
using Daybreak.Utils;
using Daybreak.Views;
using Microsoft.Win32;
using Pepa.Wpf.Utilities;
using System;
@@ -15,11 +17,13 @@ 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";
@@ -30,6 +34,7 @@ namespace Daybreak.Services.ApplicationLauncher
private readonly ICredentialManager credentialManager;
private readonly IMutexHandler mutexHandler;
private readonly ILogger logger;
private readonly IPrivilegeManager privilegeManager;
public bool IsTexmodRunning => TexModProcessDetected();
public bool IsGuildwarsRunning => this.GuildwarsProcessDetected();
@@ -39,32 +44,41 @@ namespace Daybreak.Services.ApplicationLauncher
IConfigurationManager configurationManager,
ICredentialManager credentialManager,
IMutexHandler mutexHandler,
ILogger logger)
ILogger 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.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
}
public async Task LaunchGuildwars()
public async Task<bool> LaunchGuildwars()
{
var configuration = this.configurationManager.GetConfiguration();
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)
{
if (this.privilegeManager.AdminPrivileges is false)
{
this.privilegeManager.RequestAdminPrivileges<MainView>("You need administrator rights in order to start using multi-launch");
return false;
}
ClearGwLocks();
}
LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
return await LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
},
onNone: () =>
{
throw new CredentialsNotFoundException($"No credentials available");
});
})
.ExtractValue();
}
public Task LaunchGuildwarsToolbox()
@@ -103,7 +117,34 @@ namespace Daybreak.Services.ApplicationLauncher
});
}
private void LaunchGuildwarsProcess(string email, Models.SecureString password, string character)
public void RestartDaybreakAsAdmin()
{
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.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (executable is null)
@@ -129,10 +170,54 @@ namespace Daybreak.Services.ApplicationLauncher
args.Add(character);
}
if (Process.Start(executable.Path, args) is null)
var identity = this.configurationManager.GetConfiguration().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()
@@ -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();
}
}
@@ -68,13 +68,12 @@ namespace Daybreak.Services.BuildTemplates
public void SaveBuild(BuildEntry buildEntry)
{
var encodedBuild = this.EncodeTemplate(buildEntry.Build);
File.WriteAllText($"{BuildsPath}\\{buildEntry.Name}.txt", encodedBuild);
if (string.IsNullOrWhiteSpace(buildEntry.PreviousName))
{
return;
File.Delete($"{BuildsPath}\\{buildEntry.PreviousName}.txt");
}
File.Delete($"{BuildsPath}\\{buildEntry.PreviousName}.txt");
File.WriteAllText($"{BuildsPath}\\{buildEntry.Name}.txt", encodedBuild);
}
public void RemoveBuild(BuildEntry buildEntry)
@@ -1,5 +1,4 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using System;
@@ -15,6 +14,8 @@ namespace Daybreak.Services.Configuration
private ApplicationConfiguration applicationConfiguration;
private readonly ILogger logger;
public event EventHandler ConfigurationChanged;
public ConfigurationManager(ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
@@ -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);
}
@@ -0,0 +1,11 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLifetime;
using System;
namespace Daybreak.Services.KeyboardHook
{
public interface IKeyboardHookService : IApplicationLifetimeService
{
event EventHandler<KeyboardHookEventArgs> KeyboardPressed;
}
}
@@ -0,0 +1,117 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Utils;
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 logger;
private IntPtr windowsHookHandle;
private IntPtr user32LibraryHandle;
private NativeMethods.HookProc hookProc;
public event EventHandler<KeyboardHookEventArgs> KeyboardPressed;
public KeyboardHookService(
ILogger 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 Daybreak.Services.ApplicationLifetime;
namespace Daybreak.Services.KeyboardMacros
{
public interface IMacroService : IApplicationLifetimeService
{
}
}
@@ -0,0 +1,118 @@
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Services.KeyboardHook;
using Daybreak.Services.Logging;
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
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 ILogger logger;
private readonly IKeyboardHookService keyboardHookService;
private readonly IConfigurationManager configurationManager;
private readonly HashSet<Keys> KeysDown = new();
private IEnumerable<KeyMacro> loadedMacros;
private bool gameActive, hookEnabled;
private IntPtr gwWindowHwnd;
public MacroService(
ILogger logger,
IKeyboardHookService keyboardHookService,
IConfigurationManager configurationManager)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.keyboardHookService = keyboardHookService.ThrowIfNull(nameof(keyboardHookService));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.configurationManager.ConfigurationChanged += (s, e) => this.LoadConfiguration();
this.LoadConfiguration();
this.keyboardHookService.KeyboardPressed += this.KeyboardHookService_KeyboardPressed;
this.SetupGameActiveChecker();
}
private void LoadConfiguration()
{
this.hookEnabled = this.configurationManager.GetConfiguration().ExperimentalFeatures.CanInterceptKeys;
this.loadedMacros = this.configurationManager.GetConfiguration().ExperimentalFeatures.Macros ?? new List<KeyMacro>();
}
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),
cancellationTokenSource.Token);
}
private void KeyboardHookService_KeyboardPressed(object sender, KeyboardHookEventArgs e)
{
if (this.gameActive && this.hookEnabled)
{
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.loadedMacros
.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);
}
}
}
+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);
}
}
+24 -7
View File
@@ -1,6 +1,7 @@
using Slim;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
namespace Daybreak.Services.ViewManagement
@@ -32,17 +33,33 @@ 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)
{
Application.Current.Dispatcher.Invoke(() =>
{
var view = this.serviceManager.GetService(viewType).As<UserControl>();
this.container.Children.Clear();
this.container.Children.Add(view);
view.DataContext = dataContext;
});
}
}
}
@@ -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,34 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using Daybreak.Views;
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 logger;
public PrivilegeManager(
IViewManager viewManager,
ILogger 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 });
}
}
}
@@ -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,39 @@
using Daybreak.Models;
using Daybreak.Services.Logging;
using Daybreak.Utils;
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 logger;
public IEnumerable<Screen> Screens { get; } = WpfScreenHelper.Screen.AllScreens
.Select((screen, index) => new Screen { Id = index, Size = screen.Bounds });
public ScreenManager(
ILogger 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");
}
}
}
@@ -0,0 +1,9 @@
using Daybreak.Services.ApplicationLifetime;
namespace Daybreak.Services.Shortcuts
{
public interface IShortcutManager : IApplicationLifetimeService
{
bool ShortcutEnabled { get; set; }
}
}
@@ -0,0 +1,108 @@
using Daybreak.Services.Configuration;
using ShellLink;
using System.Diagnostics;
using System.Extensions;
using System.IO;
namespace Daybreak.Services.Shortcuts
{
public sealed class ShortcutManager : IShortcutManager
{
private const string ShortcutName = "Daybreak.lnk";
private readonly IConfigurationManager configurationManager;
public bool ShortcutEnabled {
get => this.ShortcutExists();
set
{
if (value is true)
{
this.CreateShortcut();
}
else
{
this.RemoveShortcut();
}
}
}
public ShortcutManager(IConfigurationManager configurationManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.configurationManager.ConfigurationChanged += (_, _) => this.LoadConfiguration();
this.LoadConfiguration();
}
private void LoadConfiguration()
{
var shortcutEnabled = this.configurationManager.GetConfiguration().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.configurationManager.GetConfiguration().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.configurationManager.GetConfiguration().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.configurationManager.GetConfiguration().ShortcutLocation;
var shortcutPath = $"{shortcutFolder}\\{ShortcutName}";
File.Delete(shortcutPath);
}
public void OnStartup()
{
}
public void OnClosing()
{
}
}
}
@@ -21,6 +21,7 @@ namespace Daybreak.Services.Updater
{
public sealed class ApplicationUpdater : IApplicationUpdater
{
private const string LaunchActionName = "Launch_Daybreak";
private const string UpdateDesiredKey = "UpdateDesired";
private const string ExecutionPolicyKey = "ExecutionPolicy";
private const string UpdatedKey = "Updating";
@@ -32,13 +33,20 @@ 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 Url = "https://github.com/AlexMacocian/Daybreak/releases/latest";
private const string DownloadUrl = $"https://github.com/AlexMacocian/Daybreak/releases/download/v{VersionTag}/Daybreakv{VersionTag}.zip";
private const string GetExecutionPolicyCommand = "Get-ExecutionPolicy -Scope CurrentUser";
private const string SetExecutionPolicyCommand = $"Set-ExecutionPolicy {ExecutionPolicyTag} -Scope CurrentUser";
private const string WaitCommand = $"Wait-Process -Id {ProcessIdTag}";
private const string ExtractCommandTemplate = $"Expand-Archive -Path '{InputFileTag}' -DestinationPath '{OutputPathTag}' -Force";
private const string RunClientCommand = @".\Daybreak.exe";
private const string 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}";
@@ -251,8 +259,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 +275,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");
+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>
+21 -1
View File
@@ -1,4 +1,5 @@
using Daybreak.Services.Logging;
using Daybreak.Services.Privilege;
using Daybreak.Services.Runtime;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
@@ -18,18 +19,32 @@ namespace Daybreak.Views
private readonly ILogger logger;
private readonly IViewManager viewManager;
private readonly IRuntimeStore runtimeStore;
private readonly IPrivilegeManager privilegeManager;
public AskUpdateView(
ILogger logger,
IViewManager viewManager,
IRuntimeStore runtimeStore)
IRuntimeStore runtimeStore,
IPrivilegeManager privilegeManager)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.runtimeStore = runtimeStore.ThrowIfNull(nameof(runtimeStore));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
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");
@@ -41,6 +56,11 @@ namespace Daybreak.Views
{
this.logger.LogInformation("User accepted update");
this.runtimeStore.StoreValue(UpdateDesiredKey, true);
if (this.CheckIfAdmin() is false)
{
return;
}
this.viewManager.ShowView<UpdateView>();
}
}
+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>
@@ -21,10 +21,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;
public bool LaunchAsCurrentUser
{
get => this.GetTypedValue<bool>(LaunchAsCurrentUserProperty);
set => this.SetValue(LaunchAsCurrentUserProperty, value);
}
public bool MultiLaunch
{
get => this.GetTypedValue<bool>(MultiLaunchProperty);
@@ -40,6 +49,11 @@ 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,
@@ -57,6 +71,8 @@ namespace Daybreak.Views
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()
@@ -64,6 +80,8 @@ namespace Daybreak.Views
var config = this.configurationManager.GetConfiguration();
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;
+23 -2
View File
@@ -2,9 +2,11 @@
using Daybreak.Models.Builds;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Configuration;
using Daybreak.Services.Screens;
using Daybreak.Services.ViewManagement;
using System;
using System.Extensions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -40,6 +42,7 @@ namespace Daybreak.Views
private readonly IApplicationLauncher applicationDetector;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly IScreenManager screenManager;
private readonly CancellationTokenSource cancellationTokenSource = new();
private bool leftBrowserMaximized = false;
@@ -94,8 +97,10 @@ namespace Daybreak.Views
public MainView(
IApplicationLauncher applicationDetector,
IViewManager viewManager,
IConfigurationManager configurationManager)
IConfigurationManager configurationManager,
IScreenManager screenManager)
{
this.screenManager = screenManager.ThrowIfNull(nameof(screenManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.applicationDetector = applicationDetector.ThrowIfNull(nameof(applicationDetector));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
@@ -145,7 +150,23 @@ namespace Daybreak.Views
{
try
{
await this.applicationDetector.LaunchGuildwars();
if (await this.applicationDetector.LaunchGuildwars() is false)
{
return;
}
if (this.configurationManager.GetConfiguration().SetGuildwarsWindowSizeOnLaunch)
{
var id = this.configurationManager.GetConfiguration().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.configurationManager.GetConfiguration().ToolboxAutoLaunch is true)
{
var delay = this.configurationManager.GetConfiguration().ExperimentalFeatures.ToolboxAutoLaunchDelay;
+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>
@@ -0,0 +1,51 @@
using Daybreak.Models;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Logging;
using Daybreak.Services.Privilege;
using Daybreak.Services.ViewManagement;
using Daybreak.Utils;
using System.Extensions;
using System.Windows.Controls;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for RequestElevationView.xaml
/// </summary>
public partial class RequestElevationView : UserControl
{
private readonly IApplicationLauncher applicationLauncher;
private readonly IViewManager viewManager;
private readonly ILogger logger;
public RequestElevationView(
IApplicationLauncher applicationLauncher,
IViewManager viewManager,
ILogger logger)
{
this.applicationLauncher = applicationLauncher.ThrowIfNull(nameof(applicationLauncher));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.logger = logger.ThrowIfNull(nameof(logger));
this.InitializeComponent();
}
private void YesButton_Clicked(object sender, System.EventArgs e)
{
this.applicationLauncher.RestartDaybreakAsAdmin();
}
private void NoButton_Clicked(object sender, System.EventArgs e)
{
if (this.DataContext is ElevationRequest elevationRequest)
{
this.logger.LogWarning($"Elevation request denied. Showing {elevationRequest.View} with {elevationRequest.DataContext}");
this.viewManager.ShowView(elevationRequest.View, elevationRequest.DataContext);
}
else
{
this.logger.LogWarning("Elevation request context is not set. Returning to home page");
this.viewManager.ShowView<MainView>();
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
<UserControl x:Class="Daybreak.Views.ScreenChoiceView"
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">
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked"></controls:BackButton>
<TextBlock Text="Choose screen" FontSize="18" Foreground="White" HorizontalAlignment="Center"></TextBlock>
<controls:OpaqueButton Text="Test" Foreground="White" HighlightOpacity="0.3" Highlight="White" FontSize="18" Width="80"
HorizontalAlignment="Right" Margin="0, 0, 50, 0" Clicked="OpaqueButton_Clicked"
IsEnabled="{Binding ElementName=_this, Path=CanTest, Mode=OneWay}"></controls:OpaqueButton>
<controls:SaveButton Foreground="White" Height="30" Width="30" Grid.Column="1" HorizontalAlignment="Right" Margin="5"
Clicked="SaveButton_Clicked"></controls:SaveButton>
<Viewbox Grid.Row="1">
<Grid x:Name="ScreenContainer"></Grid>
</Viewbox>
</Grid>
</UserControl>
+108
View File
@@ -0,0 +1,108 @@
using Daybreak.Controls.Templates;
using Daybreak.Models;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Configuration;
using Daybreak.Services.Screens;
using Daybreak.Services.ViewManagement;
using System;
using System.Extensions;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Media;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for ScreenChoiceView.xaml
/// </summary>
public partial class ScreenChoiceView : UserControl
{
public static readonly DependencyProperty CanTestProperty =
DependencyPropertyExtensions.Register<ScreenChoiceView, bool>(nameof(CanTest));
private readonly IScreenManager screenManager;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly IApplicationLauncher applicationLauncher;
private int selectedId;
public bool CanTest
{
get => this.GetTypedValue<bool>(CanTestProperty);
set => this.SetValue(CanTestProperty, value);
}
public ScreenChoiceView(
IViewManager viewManager,
IScreenManager screenManager,
IConfigurationManager configurationManager,
IApplicationLauncher applicationLauncher)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenManager = screenManager.ThrowIfNull(nameof(screenManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.applicationLauncher = applicationLauncher.ThrowIfNull(nameof(applicationLauncher));
this.InitializeComponent();
this.selectedId = configurationManager.GetConfiguration().DesiredGuildwarsScreen;
this.CanTest = applicationLauncher.IsGuildwarsRunning;
this.SetupView();
}
private void SetupView()
{
foreach(var screen in this.screenManager.Screens)
{
var screenTemplate = new ScreenTemplate
{
DataContext = screen,
Margin = new System.Windows.Thickness(screen.Size.Left, screen.Size.Top, 0, 0),
Width = screen.Size.Width,
Height = screen.Size.Height,
VerticalAlignment = System.Windows.VerticalAlignment.Top,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
Foreground = screen.Id == this.selectedId ? Brushes.LightGreen : Brushes.White
};
screenTemplate.Clicked += ScreenTemplate_Clicked;
this.ScreenContainer.Children.Add(screenTemplate);
}
}
private void ScreenTemplate_Clicked(object sender, Screen e)
{
this.SelectScreen(e);
}
private void SelectScreen(Screen screen)
{
this.selectedId = screen.Id;
foreach(var template in this.ScreenContainer.Children.OfType<ScreenTemplate>())
{
template.Foreground = template.DataContext.As<Screen>().Id == this.selectedId ? Brushes.LightGreen : Brushes.White;
}
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsView>();
}
private void SaveButton_Clicked(object sender, EventArgs e)
{
this.configurationManager.GetConfiguration().DesiredGuildwarsScreen = this.selectedId;
this.viewManager.ShowView<SettingsCategoryView>();
}
private void OpaqueButton_Clicked(object sender, EventArgs e)
{
var screen = this.screenManager.Screens.Skip(this.selectedId).FirstOrDefault();
if (screen is null)
{
throw new InvalidOperationException($"Unable to test placement. No screen with id {this.selectedId}");
}
this.screenManager.MoveGuildwarsToScreen(screen);
}
}
}
+80 -54
View File
@@ -66,60 +66,86 @@
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Launcher settings" FontSize="22" Foreground="White" HorizontalAlignment="Center" Grid.ColumnSpan="2"></TextBlock>
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked"></controls:BackButton>
<controls:SaveButton Foreground="White" Height="30" Width="30" Grid.Column="1" HorizontalAlignment="Right" Margin="5"
Clicked="SaveButton_Clicked"></controls:SaveButton>
<StackPanel Orientation="Vertical" Grid.Row="1">
<TextBlock Text="Texmod path" FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="GWToolbox path: " FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="GWToolbox auto-launch: " FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="Browsers enabled: " FontSize="22" Foreground="White"/>
<TextBlock Text="Browsers address bar readonly: " FontSize="22" Foreground="White"/>
<TextBlock Text="Left browser homepage: " FontSize="22" Foreground="White"></TextBlock>
<TextBlock Text="Right browser homepage: " FontSize="22" Foreground="White"></TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1">
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Launcher settings" FontSize="22" Foreground="White" HorizontalAlignment="Center" Grid.ColumnSpan="2"></TextBlock>
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked"></controls:BackButton>
<controls:SaveButton Foreground="White" Height="30" Width="30" Grid.Column="1" HorizontalAlignment="Right" Margin="5"
Clicked="SaveButton_Clicked"></controls:SaveButton>
<StackPanel Orientation="Vertical" Grid.Row="1">
<TextBlock Text="Texmod path" FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="GWToolbox auto-launch: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="GWToolbox path: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Browsers enabled: " FontSize="22" Foreground="White" Height="30"/>
<TextBlock Text="Browsers address bar readonly: " FontSize="22" Foreground="White" Height="30"/>
<TextBlock Text="Left browser homepage: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Right browser homepage: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Auto-place on desired screen: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Desired screen id: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Place Shortcut" FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Shortcut folder" FontSize="22" Foreground="White" Height="30"></TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1">
<Grid Height="30">
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=TexmodPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="TexmodFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=ToolboxAutoLaunch, Mode=TwoWay}"></ToggleButton>
<Grid Height="30">
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=ToolboxPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ToolboxFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=BrowsersEnabled, Mode=TwoWay}"></ToggleButton>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=AddressBarReadonly, Mode=TwoWay}"></ToggleButton>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=TexmodPath, Mode=TwoWay}"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="TexmodFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<Grid>
FontSize="22" Background="Transparent" Foreground="White" Height="30"
Text="{Binding ElementName=_this, Path=LeftBrowserUrl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=ToolboxPath, Mode=TwoWay}"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ToolboxFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=ToolboxAutoLaunch, Mode=TwoWay}"></ToggleButton>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=BrowsersEnabled, Mode=TwoWay}"></ToggleButton>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=AddressBarReadonly, Mode=TwoWay}"></ToggleButton>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=LeftBrowserUrl, Mode=TwoWay}"
TextChanged="LeftBrowserUrl_TextChanged"></TextBox>
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=RightBrowserUrl, Mode=TwoWay}"
TextChanged="RightBrowserUrl_TextChanged"></TextBox>
</StackPanel>
</Grid>
FontSize="22" Background="Transparent" Foreground="White" Height="30"
Text="{Binding ElementName=_this, Path=RightBrowserUrl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=AutoPlaceOnScreen, Mode=TwoWay}"></ToggleButton>
<Grid Height="30">
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=DesiredScreen, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewTextInput="TextBox_AllowOnlyNumbers" Margin="0, 0, 30, 0"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ScreenPickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=ShortcutPlaced, Mode=TwoWay}"></ToggleButton>
<Grid Height="30">
<TextBox HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=ShortcutFolder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0, 0, 30, 0"></TextBox>
<controls:FilePickerGlyph Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ShortcutFolderPickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
</StackPanel>
</Grid>
</ScrollViewer>
</UserControl>
+69 -11
View File
@@ -1,4 +1,5 @@
using Daybreak.Services.Configuration;
using Daybreak.Services.Shortcuts;
using Daybreak.Services.ViewManagement;
using Microsoft.Win32;
using System;
@@ -6,13 +7,14 @@ using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using System.Windows.Forms;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for SettingsView.xaml
/// </summary>
public partial class SettingsView : UserControl
public partial class SettingsView : System.Windows.Controls.UserControl
{
public static readonly DependencyProperty TexmodPathProperty =
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(TexmodPath));
@@ -28,6 +30,14 @@ namespace Daybreak.Views
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(RightBrowserUrl));
public static readonly DependencyProperty ToolboxAutoLaunchProperty =
DependencyPropertyExtensions.Register<SettingsView, bool>(nameof(ToolboxAutoLaunch));
public static readonly DependencyProperty AutoPlaceOnScreenProperty =
DependencyPropertyExtensions.Register<SettingsView, bool>(nameof(AutoPlaceOnScreen));
public static readonly DependencyProperty DesiredScreenProperty =
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(DesiredScreen));
public static readonly DependencyProperty ShortcutFolderProperty =
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(ShortcutFolder));
public static readonly DependencyProperty ShortcutPlacedProperty =
DependencyPropertyExtensions.Register<SettingsView, bool>(nameof(ShortcutPlaced));
private readonly IConfigurationManager configurationManager;
private readonly IViewManager viewManager;
@@ -67,10 +77,31 @@ namespace Daybreak.Views
get => this.GetTypedValue<bool>(BrowsersEnabledProperty);
set => this.SetValue(BrowsersEnabledProperty, value);
}
public bool AutoPlaceOnScreen
{
get => this.GetTypedValue<bool>(AutoPlaceOnScreenProperty);
set => this.SetValue(AutoPlaceOnScreenProperty, value);
}
public string DesiredScreen
{
get => this.GetTypedValue<string>(DesiredScreenProperty);
set => this.SetValue(DesiredScreenProperty, value);
}
public string ShortcutFolder
{
get => this.GetTypedValue<string>(ShortcutFolderProperty);
set => this.SetValue(ShortcutFolderProperty, value);
}
public bool ShortcutPlaced
{
get => this.GetTypedValue<bool>(ShortcutPlacedProperty);
set => this.SetValue(ShortcutPlacedProperty, value);
}
public SettingsView(
IConfigurationManager configurationManager,
IViewManager viewManager)
IViewManager viewManager,
IShortcutManager shortcutManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
@@ -88,6 +119,11 @@ namespace Daybreak.Views
this.ToolboxAutoLaunch = config.ToolboxAutoLaunch;
this.TexmodPath = config.TexmodPath;
this.BrowsersEnabled = config.BrowsersEnabled;
this.AutoPlaceOnScreen = config.SetGuildwarsWindowSizeOnLaunch;
this.DesiredScreen = config.DesiredGuildwarsScreen.ToString();
this.ShortcutFolder = config.ShortcutLocation;
this.ShortcutPlaced = config.PlaceShortcut;
}
private void SaveButton_Clicked(object sender, EventArgs e)
@@ -100,13 +136,17 @@ namespace Daybreak.Views
currentConfig.ToolboxAutoLaunch = this.ToolboxAutoLaunch;
currentConfig.TexmodPath = this.TexmodPath;
currentConfig.BrowsersEnabled = this.BrowsersEnabled;
currentConfig.SetGuildwarsWindowSizeOnLaunch = this.AutoPlaceOnScreen;
currentConfig.DesiredGuildwarsScreen = int.Parse(this.DesiredScreen);
currentConfig.ShortcutLocation = this.ShortcutFolder;
currentConfig.PlaceShortcut = this.ShortcutPlaced;
this.configurationManager.SaveConfiguration(currentConfig);
this.viewManager.ShowView<SettingsCategoryView>();
}
private void ToolboxFilePickerGlyph_Clicked(object sender, EventArgs e)
{
var filePicker = new OpenFileDialog()
var filePicker = new Microsoft.Win32.OpenFileDialog()
{
CheckFileExists = true,
CheckPathExists = true,
@@ -121,7 +161,7 @@ namespace Daybreak.Views
private void TexmodFilePickerGlyph_Clicked(object sender, EventArgs e)
{
var filePicker = new OpenFileDialog()
var filePicker = new Microsoft.Win32.OpenFileDialog()
{
CheckFileExists = true,
CheckPathExists = true,
@@ -134,19 +174,37 @@ namespace Daybreak.Views
}
}
private void ShortcutFolderPickerGlyph_Clicked(object sender, EventArgs e)
{
var folderPicker = new FolderBrowserDialog()
{
Description = "Select shortcut folder",
UseDescriptionForTitle = true,
SelectedPath = this.ShortcutFolder,
ShowNewFolderButton = true
};
if (folderPicker.ShowDialog() is DialogResult.OK)
{
this.ShortcutFolder = folderPicker.SelectedPath;
}
}
private void ScreenPickerGlyph_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<ScreenChoiceView>();
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsCategoryView>();
}
private void LeftBrowserUrl_TextChanged(object sender, TextChangedEventArgs e)
private void TextBox_AllowOnlyNumbers(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
this.LeftBrowserUrl = sender.As<TextBox>().Text;
}
private void RightBrowserUrl_TextChanged(object sender, TextChangedEventArgs e)
{
this.RightBrowserUrl = sender.As<TextBox>().Text;
if (int.TryParse(e.Text, out _) is false)
{
e.Handled = true;
}
}
}
}
+8 -5
View File
@@ -9,14 +9,17 @@
xmlns:controls="clr-namespace:Daybreak.Controls"
Loaded="UpdateView_Loaded"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Background="White" MinHeight="200" MinWidth="400">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding ElementName=_this, Path=Description, Mode=OneWay}" Margin="10, 0, 10, 10" Foreground="Black"
TextWrapping="Wrap"></TextBlock>
<TextBlock Text="{Binding ElementName=_this, Path=Description, Mode=OneWay}" Margin="10, 0, 10, 0" Foreground="Black"
TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding ElementName=_this, Path=ProgressValue, Mode=OneWay}" Width="300" Height="20"></ProgressBar>
<controls:OpaqueButton Text="Ok" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="30" Height="20"
IsEnabled="{Binding ElementName=_this, Path=ContinueButtonEnabled, Mode=OneWay}"
Clicked="OpaqueButton_Clicked" Foreground="Black"></controls:OpaqueButton>
<controls:OpaqueButton Text="Ok" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="50" Height="25"
Clicked="OpaqueButton_Clicked" Foreground="Black" FontSize="16"
Visibility="{Binding ElementName=_this, Path=ContinueButtonEnabled, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"></controls:OpaqueButton>
</StackPanel>
</Grid>
</UserControl>
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>
+27
View File
@@ -25,33 +25,60 @@ Ability to set default page for each of the two browser windows.
Rotates screenshots from "Screenshots" folder. If no screenshots are present in the folder, downloads and rotates images from http://bloogum.net/guildwars (link to page is visible when showing images from the website).
Manages build templates. Load/save builds from the guildwars build folder. Ability to dynamically load build templates from the embeded browsers. Opens embeded browser for professions, attributes and skills when selected.
Screen placing/binding. If enabled, places guildwars executable on desired screen at launch.
Shortcut management. Can manage automatically shortcuts to desired folder.
# Examples/Usage
## Settings.
To modify any settings, press the settings button on the titlebar
![Settings button](https://i.imgur.com/0QSTvNF.png)
## Settings categories.
To adjust functionality, choose one of the settings categories
![Settings categories](https://i.imgur.com/LtPDvHY.png)
## Using the embeded browsers.
When in the main view, the browsers open the default/prefferred page. To change the prefferred page, navigate to it using one of the browsers and press the star button. The current loaded page will become the default for the selected browser. Preffered page can also be selected from settings.
![Browser default selection](https://i.imgur.com/nDnyIIL.png)
## Displaying screenshots
To display other images than the ones retrieved from "http://bloogum.net/guildwars", place images in the Screenshots folder, next to the Daybreak.exe executable. If the folder doesn't exist yet, either create it or run the launcher once so that it gets created automatically.
## Account management
To adjust accounts, go into account settings. Clicking on the star next to an account sets it as the current account.
![Account settings](https://i.imgur.com/Pwycwwr.png)
## Guildwars executable management
To adjust executables, go into Guildwars settings. Clicking on the star next to the executable path sets it as the current executable.
![Guildwars settings](https://i.imgur.com/XChX19t.png)
## Multibox/Multilaunch support
To enable multiboxing (multi-launch), go into Experimental settings. Then, switch between executables/accounts and launch them.
![Multibox toggle](https://i.imgur.com/vEFF2pb.png)
## Build management / Template management
To manage builds, go into Settings, Builds. Here you can select/remove builds.
![Builds list view](https://i.imgur.com/OFxnFcZ.png)
## Build editor
Doubleclick on one of the builds to enter the template management view. Here you can click on skills to open the wiki page of the skill. You can adjust professions, attributes and add/remove skills.
![Builds management view](https://i.imgur.com/exKWJb8.png)
## Dynamically load build from browser
To dynamically load a build, while browsing in the browsers from the main page, select and rightclick any text on the page that might be a template. If it is a template, the client will open a context menu with a button called "Load build template". Click on that button to load the build and switch to build template view.
![Dynamically load build](https://i.imgur.com/V5vvgsg.png)
## Screen selection/binding
To launch the guildwars executable to a specific screen, open Settings, Launcher Settings either fill in the "Desired screen id",
![Screen selection](https://i.imgur.com/j6PJnvm.png)
or use the selection view. If you press Test button on screen selection view, it will automatically move the currently running guildwars to the selected screen.
![Screen selection view](https://i.imgur.com/gTeCnM0.png)
## Shortcuts
To manage launcher shortcuts, use the Settings, Launcher Settings menu. If you want a different folder than the desktop, use the "Shortcut folder" setting to change to the desired place. By toggling "Shortcut placed" toggle, a shortcut will be created/deleted.
![Shortcut settings](https://i.imgur.com/AF6LX9B.png)