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

Merge remote-tracking branch 'origin/master'

This commit is contained in:
Robert 2021-01-19 19:58:26 +01:00
commit 281c18200a
11 changed files with 308 additions and 57 deletions

View File

@ -11,7 +11,7 @@
Artemis 2 adds highly configurable support for several games to a range of RGB keyboards, mice and headsets.
Artemis 1 is no longer supported and Artemis 2 is in active development. This entire readme and all websites/documents refer to Artemis 2.
### Check out our [Wiki](https://wiki.artemis-rgb.com) and more specifically, the [getting started guide](https://wiki.artemis-rgb.com/en/guides/user/introduction).
### Check out our [Wiki](https://wiki.artemis-rgb.com) and more specifically, the [getting started guide](https://wiki.artemis-rgb.com/en/guides/user).
**Pre-release download**: https://github.com/SpoinkyNL/Artemis/releases (pre-release means your profiles may break at any given time!)
**Plugin documentation**: https://artemis-rgb.com/docs/
@ -19,12 +19,20 @@ Artemis 1 is no longer supported and Artemis 2 is in active development. This en
#### Want to build? Follow these instructions
1. Create a central folder like ```C:\Repos```
2. Clone RGB.NET's development branch into ```<central folder>\RGB.NET```
2. Clone RGB.NET's [development branch](https://github.com/DarthAffe/RGB.NET/tree/Development) into ```<central folder>\RGB.NET```
3. Clone Artemis into ```<central folder>\Artemis```
5. Open ```<central folder>\RGB.NET\RGB.NET.sln``` and build with the default config
4. Open ```<central folder>\Artemis\src\Artemis.sln```
5. Restore Nuget packages
##### Alternatively in PowerShell
```powershell
git clone https://github.com/DarthAffe/RGB.NET -b Development RGB.NET
git clone https://github.com/Artemis-RGB/Artemis Artemis
dotnet build .\RGB.NET\RGB.NET.sln
dotnet build .\Artemis\src\Artemis.sln
```
For an up-to-date overview of what's currently being worked on, see the [Projects](https://github.com/SpoinkyNL/Artemis/projects) page
## Plugin development

View File

@ -2,6 +2,9 @@
using System.Threading.Tasks;
using System.Timers;
using Artemis.Core.Modules;
using Artemis.Core.Services;
using Ninject;
using Serilog;
namespace Artemis.Core
{
@ -14,9 +17,12 @@ namespace Artemis.Core
private Timer? _timer;
private bool _disposed;
private readonly object _lock = new();
private ILogger _logger;
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action)
{
_logger = CoreService.Kernel.Get<ILogger>();
Feature = feature;
Interval = interval;
Action = action;
@ -29,6 +35,8 @@ namespace Artemis.Core
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction)
{
_logger = CoreService.Kernel.Get<ILogger>();
Feature = feature;
Interval = interval;
AsyncAction = asyncAction;
@ -119,12 +127,19 @@ namespace Artemis.Core
if (Feature is Module module && !module.IsUpdateAllowed)
return;
if (Action != null)
Action(interval.TotalSeconds);
else if (AsyncAction != null)
try
{
Task task = AsyncAction(interval.TotalSeconds);
task.Wait();
if (Action != null)
Action(interval.TotalSeconds);
else if (AsyncAction != null)
{
Task task = AsyncAction(interval.TotalSeconds);
task.Wait();
}
}
catch (Exception exception)
{
_logger.Error(exception, "Timed update uncaught exception in plugin {plugin}", Feature.Plugin);
}
}
}

View File

@ -47,7 +47,7 @@ namespace Artemis.Core.Services
inputProvider.MouseScrollDataReceived += InputProviderOnMouseScrollDataReceived;
inputProvider.MouseMoveDataReceived += InputProviderOnMouseMoveDataReceived;
_inputProviders.Add(inputProvider);
inputProvider.OnKeyboardToggleStatusRequested();
}
@ -296,6 +296,17 @@ namespace Artemis.Core.Services
return modifiers;
}
public void ReleaseAll()
{
foreach (var (device, keys) in _pressedKeys.ToList())
{
foreach (KeyboardKey keyboardKey in keys)
{
InputProviderOnKeyboardDataReceived(this, new InputProviderKeyboardEventArgs(device, keyboardKey, false));
}
}
}
#endregion
#region Mouse

View File

@ -35,6 +35,11 @@ namespace Artemis.Core.Services
/// </summary>
void StopIdentify();
/// <summary>
/// Flush all currently pressed buttons/keys
/// </summary>
void ReleaseAll();
#region Events
/// <summary>

View File

@ -1,9 +1,14 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Interop;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Utilities;
using Linearstar.Windows.RawInput;
using Linearstar.Windows.RawInput.Native;
using Serilog;
@ -14,11 +19,12 @@ namespace Artemis.UI.InputProviders
public class NativeWindowInputProvider : InputProvider
{
private const int WM_INPUT = 0x00FF;
private readonly IInputService _inputService;
private readonly ILogger _logger;
private DateTime _lastMouseUpdate;
private SpongeWindow _sponge;
private System.Timers.Timer _taskManagerTimer;
public NativeWindowInputProvider(ILogger logger, IInputService inputService)
{
@ -28,10 +34,15 @@ namespace Artemis.UI.InputProviders
_sponge = new SpongeWindow();
_sponge.WndProcCalled += SpongeOnWndProcCalled;
_taskManagerTimer = new System.Timers.Timer(500);
_taskManagerTimer.Elapsed += TaskManagerTimerOnElapsed;
_taskManagerTimer.Start();
RawInputDevice.RegisterDevice(HidUsageAndPage.Keyboard, RawInputDeviceFlags.InputSink, _sponge.Handle);
RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, _sponge.Handle);
}
#region Overrides of InputProvider
/// <inheritdoc />
@ -51,6 +62,8 @@ namespace Artemis.UI.InputProviders
{
_sponge?.DestroyHandle();
_sponge = null;
_taskManagerTimer?.Dispose();
_taskManagerTimer = null;
}
base.Dispose(disposing);
@ -75,6 +88,15 @@ namespace Artemis.UI.InputProviders
}
}
private void TaskManagerTimerOnElapsed(object sender, ElapsedEventArgs e)
{
// If task manager has focus then we can't track keys properly, release everything to avoid them getting stuck
// Same goes for Idle which is what you get when you press Ctrl+Alt+Del
Process active = Process.GetProcessById(WindowUtilities.GetActiveProcessId());
if (active?.ProcessName == "Taskmgr" || active?.ProcessName == "Idle")
_inputService.ReleaseAll();
}
#region Keyboard
private void HandleKeyboardData(RawInputData data, RawInputKeyboardData keyboardData)

View File

@ -0,0 +1,37 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Dialogs.UpdateDialogView"
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:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="16">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap">
Update available
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
Margin="0 20 0 20"
TextWrapping="Wrap">
A new Artemis update is available! 🥳<LineBreak/>
You are currently running build <Run Text="{Binding CurrentBuild, Mode=OneWay}" /> while the latest build is <Run Text="{Binding LatestBuild, Mode=OneWay}" />. <LineBreak/> <LineBreak/>
Updating Artemis will give you the latest bug(fixes), features and improvements.
</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 8 0 0">
<Button Style="{StaticResource MaterialDesignFlatButton}"
Focusable="False"
IsCancel="True"
Command="{s:Action Cancel}"
Content="LATER" />
<Button x:Name="ConfirmButton"
Style="{StaticResource MaterialDesignFlatButton}"
IsDefault="True"
Focusable="True"
Command="{s:Action Update}"
Content="UPDATE" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,55 @@
using System;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Services;
using Artemis.UI.Shared.Services;
using Newtonsoft.Json.Linq;
namespace Artemis.UI.Screens.Settings.Dialogs
{
public class UpdateDialogViewModel : DialogViewModelBase
{
private readonly JToken _buildInfo;
private readonly IDialogService _dialogService;
private readonly IUpdateService _updateService;
private bool _canUpdate = true;
public UpdateDialogViewModel(JToken buildInfo, IUpdateService updateService, IDialogService dialogService)
{
_buildInfo = buildInfo;
_updateService = updateService;
_dialogService = dialogService;
CurrentBuild = Constants.BuildInfo.BuildNumberDisplay;
LatestBuild = buildInfo?.SelectToken("value[0].buildNumber")?.Value<string>();
}
public string CurrentBuild { get; }
public string LatestBuild { get; }
public bool CanUpdate
{
get => _canUpdate;
set => SetAndNotify(ref _canUpdate, value);
}
public async Task Update()
{
try
{
CanUpdate = false;
await _updateService.ApplyUpdate();
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("An exception occurred while applying the update", e);
}
finally
{
CanUpdate = true;
}
Session.Close();
}
}
}

View File

@ -276,7 +276,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
return;
CanOfferUpdatesIfFound = false;
bool updateFound = await _updateService.OfferUpdatesIfFound();
bool updateFound = await _updateService.OfferUpdateIfFound();
if (!updateFound)
_messageService.ShowMessage("You are already running the latest Artemis build. (☞゚ヮ゚)☞");
CanOfferUpdatesIfFound = true;

View File

@ -1,9 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.Settings.Dialogs;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
@ -36,46 +42,25 @@ namespace Artemis.UI.Services
_checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged;
}
public async Task<double> GetLatestBuildNumber()
public async Task<bool> AutoUpdate()
{
// TODO: The URL is hardcoded, that should change in the future
string latestBuildUrl = ApiUrl + "build/builds?api-version=6.1-preview.6&branchName=refs/heads/master&resultFilter=succeeded&$top=1";
_logger.Debug("Getting latest build number from {latestBuildUrl}", latestBuildUrl);
if (!_checkForUpdates.Value)
return false;
// Make the request
using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(latestBuildUrl);
// Ensure it returned correctly
if (!httpResponseMessage.IsSuccessStatusCode)
{
_logger.Warning("Failed to check for updates, request returned {statusCode}", httpResponseMessage.StatusCode);
return 0;
}
// Parse the response
string response = await httpResponseMessage.Content.ReadAsStringAsync();
try
{
JToken buildNumberToken = JObject.Parse(response).SelectToken("value[0].buildNumber");
if (buildNumberToken != null)
return buildNumberToken.Value<double>();
_logger.Warning("Failed to find build number at \"value[0].buildNumber\"");
return 0;
}
catch (Exception e)
{
_logger.Warning(e, "Failed to retrieve build info JSON");
return 0;
}
return await OfferUpdateIfFound();
}
public async Task<bool> OfferUpdatesIfFound()
public async Task<bool> OfferUpdateIfFound()
{
_logger.Information("Checking for updates");
double buildNumber = await GetLatestBuildNumber();
JToken buildInfo = await GetBuildInfo(1);
JToken buildNumberToken = buildInfo?.SelectToken("value[0].buildNumber");
if (buildNumberToken == null)
throw new ArtemisUIException("Failed to find build number at \"value[0].buildNumber\"");
double buildNumber = buildNumberToken.Value<double>();
string buildNumberDisplay = buildNumber.ToString(CultureInfo.InvariantCulture);
_logger.Information("Latest build is {buildNumber}, we're running {localBuildNumber}", buildNumberDisplay, Constants.BuildInfo.BuildNumberDisplay);
@ -83,8 +68,7 @@ namespace Artemis.UI.Services
return false;
if (_windowService.IsMainWindowOpen)
{
}
await OfferUpdate(buildInfo);
else if (_autoInstallUpdates.Value)
{
// Lets go
@ -93,6 +77,7 @@ namespace Artemis.UI.Services
$"Build {buildNumberDisplay} is available, currently on {Constants.BuildInfo.BuildNumberDisplay}.",
PackIconKind.Update
);
await ApplyUpdate();
}
else
{
@ -107,23 +92,112 @@ namespace Artemis.UI.Services
return true;
}
private async Task OfferUpdate(JToken buildInfo)
{
await _dialogService.ShowDialog<UpdateDialogViewModel>(new Dictionary<string, object> {{"buildInfo", buildInfo}});
}
public async Task<bool> IsUpdateAvailable()
{
double buildNumber = await GetLatestBuildNumber();
return buildNumber > Constants.BuildInfo.BuildNumber;
JToken buildInfo = await GetBuildInfo(1);
JToken buildNumberToken = buildInfo?.SelectToken("value[0].buildNumber");
if (buildNumberToken != null)
return buildNumberToken.Value<double>() > Constants.BuildInfo.BuildNumber;
_logger.Warning("IsUpdateAvailable: Failed to find build number at \"value[0].buildNumber\"");
return false;
}
public void ApplyUpdate()
public async Task ApplyUpdate()
{
throw new NotImplementedException();
_logger.Information("ApplyUpdate: Applying update");
// Ensure the installer is up-to-date, get installer build info
JToken buildInfo = await GetBuildInfo(6);
JToken finishTimeToken = buildInfo?.SelectToken("value[0].finishTime");
string installerPath = Path.Combine(Constants.ApplicationFolder, "Installer", "Artemis.Installer.exe");
// Always update installer if it is missing ^^
if (!File.Exists(installerPath))
await UpdateInstaller();
// Compare the creation date of the installer with the build date and update if needed
else
{
if (finishTimeToken == null)
_logger.Warning("ApplyUpdate: Failed to find build finish time at \"value[0].finishTime\", not updating the installer.");
else if (File.GetLastWriteTime(installerPath) < finishTimeToken.Value<DateTime>())
await UpdateInstaller();
}
_logger.Information("ApplyUpdate: Running installer at {installerPath}", installerPath);
try
{
Process.Start(new ProcessStartInfo(installerPath, "-autoupdate")
{
UseShellExecute = true,
Verb = "runas"
});
}
catch (Win32Exception e)
{
if (e.NativeErrorCode == 0x4c7)
_logger.Warning("ApplyUpdate: Operation was cancelled, user likely clicked No in UAC dialog.");
else
throw;
}
}
public async Task<bool> AutoUpdate()
private async Task UpdateInstaller()
{
if (!_checkForUpdates.Value)
return false;
string downloadUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe";
string installerDirectory = Path.Combine(Constants.ApplicationFolder, "Installer");
string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe");
return await OfferUpdatesIfFound();
_logger.Information("UpdateInstaller: Downloading installer from {downloadUrl}", downloadUrl);
using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(downloadUrl);
if (!httpResponseMessage.IsSuccessStatusCode)
throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}");
_logger.Information("UpdateInstaller: Writing installer file to {installerPath}", installerPath);
if (File.Exists(installerPath))
File.Delete(installerPath);
else if (!Directory.Exists(installerDirectory))
Directory.CreateDirectory(installerDirectory);
await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None);
await httpResponseMessage.Content.CopyToAsync(fs);
}
private async Task<JObject> GetBuildInfo(int buildDefinition)
{
string latestBuildUrl = ApiUrl + $"build/builds?definitions=6&resultFilter=succeeded&$top={buildDefinition}&api-version=6.1-preview.6";
_logger.Debug("GetBuildInfo: Getting build info from {latestBuildUrl}", latestBuildUrl);
// Make the request
using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(latestBuildUrl);
// Ensure it returned correctly
if (!httpResponseMessage.IsSuccessStatusCode)
{
_logger.Warning("GetBuildInfo: Getting build info, request returned {statusCode}", httpResponseMessage.StatusCode);
return null;
}
// Parse the response
string response = await httpResponseMessage.Content.ReadAsStringAsync();
try
{
return JObject.Parse(response);
}
catch (Exception e)
{
_logger.Warning(e, "GetBuildInfo: Failed to retrieve build info JSON");
return null;
}
}
#region Event handlers
@ -145,13 +219,13 @@ namespace Artemis.UI.Services
public interface IUpdateService : IArtemisUIService
{
Task<bool> OfferUpdatesIfFound();
Task<bool> IsUpdateAvailable();
void ApplyUpdate();
/// <summary>
/// If auto-update is enabled this will offer updates if found
/// </summary>
Task<bool> AutoUpdate();
Task<bool> OfferUpdateIfFound();
Task<bool> IsUpdateAvailable();
Task ApplyUpdate();
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Artemis.UI.Utilities
{
public static class WindowUtilities
{
public static int GetActiveProcessId()
{
// Get foreground window handle
IntPtr hWnd = GetForegroundWindow();
GetWindowThreadProcessId(hWnd, out uint processId);
return (int) processId;
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
}
}