diff --git a/.github/workflows/publish-hc.yml b/.github/workflows/publish-hc.yml
index cd5d237cb..9ae122118 100644
--- a/.github/workflows/publish-hc.yml
+++ b/.github/workflows/publish-hc.yml
@@ -103,4 +103,4 @@ jobs:
draft: true
fail_on_unmatched_files: true
files: |
- ./install/HandheldCompanion-${{ inputs.releaseVersion }}.exe
+ ./install/HandheldCompanion-${{ inputs.releaseVersion }}.exe
\ No newline at end of file
diff --git a/HandheldCompanion.iss b/HandheldCompanion.iss
index 0341d81dc..b4f9d242c 100644
--- a/HandheldCompanion.iss
+++ b/HandheldCompanion.iss
@@ -215,28 +215,15 @@ begin
Result := ShellExec('', ExpandConstant('{tmp}{\}') + 'netcorecheck' + Dependency_ArchSuffix + '.exe', Version, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end;
-procedure Dependency_AddDotNet70;
+procedure Dependency_AddDotNet80Desktop;
begin
- // https://dotnet.microsoft.com/download/dotnet/7.0
- if not Dependency_IsNetCoreInstalled('Microsoft.NETCore.App 7.0.0') then begin
- Dependency_Add('dotnet70' + Dependency_ArchSuffix + '.exe',
+ // https://dotnet.microsoft.com/en-us/download/dotnet/8.0
+ if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 8.0.0') then begin
+ Dependency_Add('dotNet80desktop' + Dependency_ArchSuffix + '.exe',
'/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
- '.NET Runtime 7.0.0' + Dependency_ArchTitle,
- Dependency_String('https://download.visualstudio.microsoft.com/download/pr/75c0d7c7-9f30-46fd-9675-a301f0e051f4/ec04d5cc40aa6537a4af21fad6bf8ba9/dotnet-runtime-7.0.0-win-x86.exe',
- 'https://download.visualstudio.microsoft.com/download/pr/87bc5966-97cc-498c-8381-bff4c43aafc6/baca88b989e7d2871e989d33a667d8e9/dotnet-runtime-7.0.0-win-x64.exe'),
- '', False, False);
- end;
-end;
-
-procedure Dependency_AddDotNet70Desktop;
-begin
- // https://dotnet.microsoft.com/download/dotnet/7.0
- if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 7.0.10') then begin
- Dependency_Add('dotnet70desktop' + Dependency_ArchSuffix + '.exe',
- '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
- '.NET Desktop Runtime 7.0.0' + Dependency_ArchTitle,
- Dependency_String('https://download.visualstudio.microsoft.com/download/pr/9812249d-fc42-41ab-bd2e-6e858d5dd5a7/95fa5a1a77eace4482bcb98ede190003/windowsdesktop-runtime-7.0.10-win-x86.exe',
- 'https://download.visualstudio.microsoft.com/download/pr/747f4a98-2586-4bc6-b828-34f35e384a7d/44225cfd9d365855ec77d00c4812133c/windowsdesktop-runtime-7.0.10-win-x64.exe'),
+ '.NET Desktop Runtime 8.0.0' + Dependency_ArchTitle,
+ Dependency_String('https://download.visualstudio.microsoft.com/download/pr/b280d97f-25a9-4ab7-8a12-8291aa3af117/a37ed0e68f51fcd973e9f6cb4f40b1a7/windowsdesktop-runtime-8.0.0-win-x64.exe',
+ 'https://download.visualstudio.microsoft.com/download/pr/f9e3b581-059d-429f-9f0d-1d1167ff7e32/bd7661030cd5d66cd3eee0fd20b24540/windowsdesktop-runtime-8.0.0-win-x86.exe'),
'', False, False);
end;
end;
@@ -325,21 +312,19 @@ end;
procedure Dependency_AddHideHide;
begin
- // https://www.microsoft.com/en-US/download/details.aspx?id=35
Dependency_Add('HidHide_1.4.192_x64.exe',
'/quiet /norestart',
- 'HidHide Drivers v1.4.192',
+ 'HidHide Drivers',
'https://github.com/nefarius/HidHide/releases/download/v1.4.192.0/HidHide_1.4.192_x64.exe',
'', True, False);
end;
procedure Dependency_AddViGem;
begin
- // https://www.microsoft.com/en-US/download/details.aspx?id=35
Dependency_Add('ViGEmBus_1.22.0_x64_x86_arm64.exe',
'/quiet /norestart',
- 'ViGEmBus Setup 1.22.0',
- 'https://github.com/Valkirie/HandheldCompanion/raw/main/redist/ViGEmBus_1.22.0_x64_x86_arm64.exe',
+ 'ViGEmBus Setup',
+ 'https://github.com/nefarius/ViGEmBus/releases/download/v1.22.0/ViGEmBus_1.22.0_x64_x86_arm64.exe',
'', True, False);
end;
@@ -370,7 +355,7 @@ end;
; requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
#define UseNetCoreCheck
#ifdef UseNetCoreCheck
- #define UseDotNet70
+ #define UseDotNet80
#endif
;#define UseVC2005
@@ -389,15 +374,15 @@ end;
#define MyAppSetupName 'Handheld Companion'
#define MyBuildId 'HandheldCompanion'
-#define MyAppVersion '0.18.0.6'
+#define MyAppVersion '0.19.1.3'
#define MyAppPublisher 'BenjaminLSR'
#define MyAppCopyright 'Copyright @ BenjaminLSR'
#define MyAppURL 'https://github.com/Valkirie/HandheldCompanion'
#define MyAppExeName "HandheldCompanion.exe"
#define MyConfiguration "Release"
-#ifdef UseDotNet70
- #define MyConfigurationExt "net7.0"
+#ifdef UseDotNet80
+ #define MyConfigurationExt "net8.0"
#endif
AppName={#MyAppSetupName}
@@ -419,7 +404,10 @@ SourceDir=redist
OutputDir={#SourcePath}\install
AllowNoIcons=yes
MinVersion=6.0
-PrivilegesRequired=admin
+;PrivilegesRequired=admin
+PrivilegesRequiredOverridesAllowed=dialog
+Compression=lzma
+SolidCompression=yes
// remove next line if you only deploy 32-bit binaries and dependencies
ArchitecturesInstallIn64BitMode=x64
@@ -454,12 +442,8 @@ Name: "{commondesktop}\{#MyAppSetupName}"; Filename: "{app}\{#MyAppExeName}"; Ta
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"
-[Run]
-Filename: "{sys}\sc.exe"; Parameters: "stop ControllerService" ; Flags: runascurrentuser runhidden
-Filename: "{sys}\sc.exe"; Parameters: "delete ControllerService" ; Flags: runascurrentuser runhidden
-
[UninstallRun]
-Filename: "C:\Program Files\Nefarius Software Solutions e.U\HidHideCLI\HidHideCLI.exe"; Parameters: "--cloak-off" ; RunOnceId: "CloakOff"; Flags: runascurrentuser runhidden
+Filename: "C:\Program Files\Nefarius Software Solutions\HidHide\x64\HidHideCLI.exe"; Parameters: "--cloak-off" ; RunOnceId: "CloakOff"; Flags: runascurrentuser runhidden
[UninstallDelete]
Type: filesandordirs; Name: "{app}"
@@ -508,7 +492,7 @@ begin
if not(keepHidhideCheckbox.Checked) then
begin
- if(ShellExec('', 'msiexec.exe', '/X{50D7EB6D-6A4A-4A38-B09C-CC28F75F082E} /qn /norestart', '', SW_SHOW, ewWaitUntilTerminated, resultCode)) then
+ if(ShellExec('', 'msiexec.exe', '/X{BE49B9DE-F8EB-4F54-B312-DD4B601985FC}', '', SW_SHOW, ewWaitUntilTerminated, resultCode)) then
begin
log('Successfully executed Hidhide uninstaller');
if(resultCode = 0) then
@@ -524,7 +508,7 @@ begin
if not(keepVigemCheckbox.Checked) then
begin
- if(ShellExec('', 'msiexec.exe', '/X{9C581C76-2D68-40F8-AA6F-94D3C5215C05} /qn /norestart', '', SW_SHOW, ewWaitUntilTerminated, resultCode)) then
+ if(ShellExec('', 'msiexec.exe', '/X{966606F3-2745-49E9-BF15-5C3EAA4E9077}', '', SW_SHOW, ewWaitUntilTerminated, resultCode)) then
begin
log('Successfully executed Vigem uninstaller');
if(resultCode = 0) then
@@ -564,8 +548,8 @@ end;
function InitializeSetup: Boolean;
begin
-#ifdef UseDotNet70
- Dependency_AddDotNet70Desktop;
+#ifdef UseDotNet80
+ Dependency_AddDotNet80Desktop;
#endif
#ifdef UseVC2005
@@ -610,4 +594,4 @@ begin
Result := True;
end;
-#endif
+#endif
\ No newline at end of file
diff --git a/HandheldCompanion/Actions/ButtonActions.cs b/HandheldCompanion/Actions/ButtonActions.cs
index 90f46dff5..5e56ea707 100644
--- a/HandheldCompanion/Actions/ButtonActions.cs
+++ b/HandheldCompanion/Actions/ButtonActions.cs
@@ -24,9 +24,9 @@ public ButtonActions(ButtonFlags button) : this()
this.Button = button;
}
- public override void Execute(ButtonFlags button, bool value, int longTime)
+ public override void Execute(ButtonFlags button, bool value)
{
- base.Execute(button, value, longTime);
+ base.Execute(button, value);
switch (this.Value)
{
diff --git a/HandheldCompanion/Actions/IActions.cs b/HandheldCompanion/Actions/IActions.cs
index fca93b499..98e03a224 100644
--- a/HandheldCompanion/Actions/IActions.cs
+++ b/HandheldCompanion/Actions/IActions.cs
@@ -76,10 +76,6 @@ public abstract class IActions : ICloneable
protected object Value;
protected object prevValue;
- // values below are common for button type actions
-
- protected int Period;
-
// TODO: multiple delay, delay ranges
public PressType PressType = PressType.Short;
public int LongPressTime = 450; // default value for steam
@@ -101,7 +97,6 @@ public abstract class IActions : ICloneable
public IActions()
{
- Period = TimerManager.GetPeriod();
}
public virtual void SetHaptic(ButtonFlags button, bool up)
@@ -113,42 +108,23 @@ public virtual void SetHaptic(ButtonFlags button, bool up)
ControllerManager.GetTargetController()?.SetHaptic(this.HapticStrength, button);
}
- // if longDelay == 0 no new logic will be executed
- public virtual void Execute(ButtonFlags button, bool value, int longTime)
+ public virtual void Execute(ButtonFlags button, bool value)
{
- // reset failed attempts on button release
- if (pressTimer >= 0 && !value &&
- ((PressType == PressType.Short && pressTimer >= longTime) ||
- (PressType == PressType.Long && pressTimer < longTime)))
- {
- pressTimer = -1;
- prevValue = false;
- return;
- }
-
- // some long presses exist and button was just pressed, start the timer and quit
- if (longTime > 0 && value && !(bool)prevValue)
- {
- pressTimer = 0;
- prevValue = true;
- return;
- }
-
- if (pressTimer >= 0)
+ switch(PressType)
{
- pressTimer += Period;
-
- // conditions were met to trigger either short or long, reset state, press buttons
- if ((!value && PressType == PressType.Short && pressTimer < longTime) ||
- (value && PressType == PressType.Long && pressTimer >= longTime))
- {
- pressTimer = -1;
- prevValue = false; // simulate a situation where the button was just pressed
- value = true; // prev = false, current = true, this way toggle works
- }
- // timer active, conditions not met, carry on, maybe smth happens, maybe failed attempt
- else
- return;
+ case PressType.Long:
+ {
+ if (value || (pressTimer <= LongPressTime && pressTimer >= 0))
+ {
+ pressTimer += TimerManager.GetPeriod();
+ value = true;
+ }
+ else if(pressTimer >= LongPressTime)
+ {
+ pressTimer = -1;
+ }
+ }
+ break;
}
if (Toggle)
@@ -166,7 +142,7 @@ public virtual void Execute(ButtonFlags button, bool value, int longTime)
if (TurboIdx % TurboDelay == 0)
IsTurboed = !IsTurboed;
- TurboIdx += Period;
+ TurboIdx += TimerManager.GetPeriod();
}
else
{
diff --git a/HandheldCompanion/Actions/KeyboardActions.cs b/HandheldCompanion/Actions/KeyboardActions.cs
index 0c9acf3f1..d58a37739 100644
--- a/HandheldCompanion/Actions/KeyboardActions.cs
+++ b/HandheldCompanion/Actions/KeyboardActions.cs
@@ -31,9 +31,9 @@ public KeyboardActions(VirtualKeyCode key) : this()
this.Key = key;
}
- public override void Execute(ButtonFlags button, bool value, int longTime)
+ public override void Execute(ButtonFlags button, bool value)
{
- base.Execute(button, value, longTime);
+ base.Execute(button, value);
switch (this.Value)
{
diff --git a/HandheldCompanion/Actions/MouseActions.cs b/HandheldCompanion/Actions/MouseActions.cs
index a38901699..fdc4941be 100644
--- a/HandheldCompanion/Actions/MouseActions.cs
+++ b/HandheldCompanion/Actions/MouseActions.cs
@@ -72,9 +72,9 @@ public MouseActions(MouseActionsType type) : this()
this.MouseType = type;
}
- public override void Execute(ButtonFlags button, bool value, int longTime)
+ public override void Execute(ButtonFlags button, bool value)
{
- base.Execute(button, value, longTime);
+ base.Execute(button, value);
switch (this.Value)
{
diff --git a/HandheldCompanion/App.config b/HandheldCompanion/App.config
index 2fc584b12..bf7669c66 100644
--- a/HandheldCompanion/App.config
+++ b/HandheldCompanion/App.config
@@ -141,7 +141,7 @@
True
- True
+ False
True
@@ -197,8 +197,8 @@
False
-
- False
+
+ True
False
@@ -218,6 +218,33 @@
127.0.0.1
+
+ 50
+
+
+ #FFFFFF00
+
+
+ False
+
+
+ 0
+
+
+ True
+
+
+ #FFFFFF00
+
+
+ False
+
+
+ 50
+
+
+ False
+
\ No newline at end of file
diff --git a/HandheldCompanion/Controllers/DInputController.cs b/HandheldCompanion/Controllers/DInputController.cs
index 9e5bdb903..966bcb580 100644
--- a/HandheldCompanion/Controllers/DInputController.cs
+++ b/HandheldCompanion/Controllers/DInputController.cs
@@ -17,7 +17,7 @@ public DInputController(Joystick joystick, PnPDetails details)
return;
this.joystick = joystick;
- UserIndex = joystick.Properties.JoystickId;
+ UserIndex = (byte)joystick.Properties.JoystickId;
if (details is null)
return;
@@ -28,9 +28,9 @@ public DInputController(Joystick joystick, PnPDetails details)
// Set BufferSize in order to use buffered data.
joystick.Properties.BufferSize = 128;
- // ui
- DrawControls();
- RefreshControls();
+ // UI
+ DrawUI();
+ UpdateUI();
}
public override string ToString()
diff --git a/HandheldCompanion/Controllers/DS4Controller.cs b/HandheldCompanion/Controllers/DS4Controller.cs
index f43ba69a7..7028dc7b5 100644
--- a/HandheldCompanion/Controllers/DS4Controller.cs
+++ b/HandheldCompanion/Controllers/DS4Controller.cs
@@ -1,7 +1,6 @@
using HandheldCompanion.Inputs;
using HandheldCompanion.Managers;
using HandheldCompanion.Utils;
-using Inkore.UI.WPF.Modern;
using System.Windows;
using System.Windows.Media;
using static JSL;
@@ -87,6 +86,11 @@ public override void UpdateInputs(long ticks)
base.UpdateInputs(ticks);
}
+ public override string ToString()
+ {
+ return "DUALSHOCK®4 Wireless Controller";
+ }
+
public override void Plug()
{
TimerManager.Tick += UpdateInputs;
diff --git a/HandheldCompanion/Controllers/DualSenseController.cs b/HandheldCompanion/Controllers/DualSenseController.cs
index 48755e2e8..2c9884e36 100644
--- a/HandheldCompanion/Controllers/DualSenseController.cs
+++ b/HandheldCompanion/Controllers/DualSenseController.cs
@@ -79,6 +79,11 @@ public override void UpdateInputs(long ticks)
base.UpdateInputs(ticks);
}
+ public override string ToString()
+ {
+ return "DualSense® Wireless Controller";
+ }
+
public override void Plug()
{
TimerManager.Tick += UpdateInputs;
diff --git a/HandheldCompanion/Controllers/GordonController.cs b/HandheldCompanion/Controllers/GordonController.cs
index a30f26ecd..637c007f8 100644
--- a/HandheldCompanion/Controllers/GordonController.cs
+++ b/HandheldCompanion/Controllers/GordonController.cs
@@ -22,16 +22,7 @@ public class GordonController : SteamController
public GordonController(PnPDetails details) : base()
{
- if (details is null)
- return;
-
- Controller = new(details.attributes.VendorID, details.attributes.ProductID, details.GetMI());
-
- // open controller
- Open();
-
- Details = details;
- Details.isHooked = true;
+ AttachDetails(details);
// UI
ColoredButtons.Add(ButtonFlags.B1, new SolidColorBrush(Color.FromArgb(255, 81, 191, 61)));
@@ -39,9 +30,8 @@ public GordonController(PnPDetails details) : base()
ColoredButtons.Add(ButtonFlags.B3, new SolidColorBrush(Color.FromArgb(255, 26, 159, 255)));
ColoredButtons.Add(ButtonFlags.B4, new SolidColorBrush(Color.FromArgb(255, 255, 200, 44)));
- InitializeComponent();
- DrawControls();
- RefreshControls();
+ DrawUI();
+ UpdateUI();
// Additional controller specific source buttons/axes
SourceButtons.AddRange(new List() { ButtonFlags.L4, ButtonFlags.R4 });
@@ -70,6 +60,16 @@ public GordonController(PnPDetails details) : base()
SourceAxis.Remove(AxisLayoutFlags.RightStick);
}
+ public override void AttachDetails(PnPDetails details)
+ {
+ base.AttachDetails(details);
+
+ Controller = new(details.VendorID, details.ProductID, details.GetMI());
+
+ // open controller
+ Open();
+ }
+
public override string ToString()
{
string baseName = base.ToString();
diff --git a/HandheldCompanion/Controllers/IController.xaml b/HandheldCompanion/Controllers/IController.xaml
index 57033b1a5..595e0f0fd 100644
--- a/HandheldCompanion/Controllers/IController.xaml
+++ b/HandheldCompanion/Controllers/IController.xaml
@@ -4,8 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:HandheldCompanion.Controllers"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:HandheldCompanion.Properties"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
d:DesignHeight="450"
d:DesignWidth="800"
@@ -16,7 +16,7 @@
Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}">
-
+
@@ -25,16 +25,27 @@
-
-
+ FontFamily="PromptFont"
+ FontSize="30"
+ Glyph="" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -43,6 +54,7 @@
Width="100"
Click="ui_button_hook_Click"
FontSize="14"
+ Content="{x:Static resx:Resources.Controller_Connect}"
Style="{DynamicResource AccentButtonStyle}" />
-
+
diff --git a/HandheldCompanion/Controllers/IController.xaml.cs b/HandheldCompanion/Controllers/IController.xaml.cs
index 254e7cd71..1d81c958c 100644
--- a/HandheldCompanion/Controllers/IController.xaml.cs
+++ b/HandheldCompanion/Controllers/IController.xaml.cs
@@ -29,19 +29,19 @@ public partial class IController : UserControl
// Buttons and axes we should be able to map to.
// When we have target controllers with different buttons (e.g. in VigEm) this will have to be moved elsewhere.
public static readonly List TargetButtons = new()
- {
- ButtonFlags.B1, ButtonFlags.B2, ButtonFlags.B3, ButtonFlags.B4,
- ButtonFlags.DPadUp, ButtonFlags.DPadDown, ButtonFlags.DPadLeft, ButtonFlags.DPadRight,
- ButtonFlags.Start, ButtonFlags.Back, ButtonFlags.Special,
- ButtonFlags.L1, ButtonFlags.R1,
- ButtonFlags.LeftStickClick, ButtonFlags.RightStickClick,
- };
+ {
+ ButtonFlags.B1, ButtonFlags.B2, ButtonFlags.B3, ButtonFlags.B4,
+ ButtonFlags.DPadUp, ButtonFlags.DPadDown, ButtonFlags.DPadLeft, ButtonFlags.DPadRight,
+ ButtonFlags.Start, ButtonFlags.Back, ButtonFlags.Special,
+ ButtonFlags.L1, ButtonFlags.R1,
+ ButtonFlags.LeftStickClick, ButtonFlags.RightStickClick,
+ };
public static readonly List TargetAxis = new()
- {
- AxisLayoutFlags.LeftStick, AxisLayoutFlags.RightStick,
- AxisLayoutFlags.L2, AxisLayoutFlags.R2,
- };
+ {
+ AxisLayoutFlags.LeftStick, AxisLayoutFlags.RightStick,
+ AxisLayoutFlags.L2, AxisLayoutFlags.R2,
+ };
public static readonly string defaultGlyph = "\u2753";
@@ -59,36 +59,91 @@ public partial class IController : UserControl
public ButtonState InjectedButtons = new();
public ControllerState Inputs = new();
- protected bool isPlugged;
+ public virtual bool IsReady => true;
+
+ public bool IsBusy
+ {
+ get
+ {
+ return IsEnabled;
+ }
+ set
+ {
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ IsEnabled = !value;
+ ProgressBarPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
+ });
+ }
+ }
protected List SourceAxis = new()
- {
- // same as target, we assume all controllers have those axes
- AxisLayoutFlags.LeftStick, AxisLayoutFlags.RightStick,
- AxisLayoutFlags.L2, AxisLayoutFlags.R2
- };
+ {
+ // same as target, we assume all controllers have those axes
+ AxisLayoutFlags.LeftStick, AxisLayoutFlags.RightStick,
+ AxisLayoutFlags.L2, AxisLayoutFlags.R2
+ };
// Buttons and axes all controllers have that we can map.
// Additional ones can be added per controller.
protected List SourceButtons = new()
- {
- // same as target, we assume all controllers have those buttons
- ButtonFlags.B1, ButtonFlags.B2, ButtonFlags.B3, ButtonFlags.B4,
- ButtonFlags.DPadUp, ButtonFlags.DPadDown, ButtonFlags.DPadLeft, ButtonFlags.DPadRight,
- ButtonFlags.Start, ButtonFlags.Back, ButtonFlags.Special,
- ButtonFlags.L1, ButtonFlags.R1,
- ButtonFlags.LeftStickClick, ButtonFlags.RightStickClick,
- // additional buttons calculated from the above
- ButtonFlags.L2Soft, ButtonFlags.R2Soft, ButtonFlags.L2Full, ButtonFlags.R2Full,
- ButtonFlags.LeftStickUp, ButtonFlags.LeftStickDown, ButtonFlags.LeftStickLeft, ButtonFlags.LeftStickRight,
- ButtonFlags.RightStickUp, ButtonFlags.RightStickDown, ButtonFlags.RightStickLeft, ButtonFlags.RightStickRight
- };
-
- protected int UserIndex;
+ {
+ // same as target, we assume all controllers have those buttons
+ ButtonFlags.B1, ButtonFlags.B2, ButtonFlags.B3, ButtonFlags.B4,
+ ButtonFlags.DPadUp, ButtonFlags.DPadDown, ButtonFlags.DPadLeft, ButtonFlags.DPadRight,
+ ButtonFlags.Start, ButtonFlags.Back, ButtonFlags.Special,
+ ButtonFlags.L1, ButtonFlags.R1,
+ ButtonFlags.LeftStickClick, ButtonFlags.RightStickClick,
+ // additional buttons calculated from the above
+ ButtonFlags.L2Soft, ButtonFlags.R2Soft, ButtonFlags.L2Full, ButtonFlags.R2Full,
+ ButtonFlags.LeftStickUp, ButtonFlags.LeftStickDown, ButtonFlags.LeftStickLeft, ButtonFlags.LeftStickRight,
+ ButtonFlags.RightStickUp, ButtonFlags.RightStickDown, ButtonFlags.RightStickLeft, ButtonFlags.RightStickRight
+ };
+
+ private byte _UserIndex = 255;
+ protected byte UserIndex
+ {
+ get
+ {
+ return _UserIndex;
+ }
+ set
+ {
+ _UserIndex = value;
+ UserIndexChanged?.Invoke(value);
+
+ // UI thread (async)
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ foreach(FrameworkElement frameworkElement in UserIndexPanel.Children)
+ {
+ if (frameworkElement is not Border)
+ continue;
+
+ Border border = (Border)frameworkElement;
+ int idx = UserIndexPanel.Children.IndexOf(border);
+
+ if (idx == value)
+ border.SetResourceReference(BackgroundProperty, "AccentAAFillColorDefaultBrush");
+ else
+ border.SetResourceReference(BackgroundProperty, "SystemControlForegroundBaseLowBrush");
+ }
+ });
+ }
+ }
+
protected double VibrationStrength = 1.0d;
public IController()
{
+ InitializeComponent();
+ }
+
+ public virtual void AttachDetails(PnPDetails details)
+ {
+ this.Details = details;
+ Details.isHooked = true;
}
public virtual void UpdateInputs(long ticks)
@@ -101,6 +156,11 @@ public bool HasMotionSensor()
return Capabilities.HasFlag(ControllerCapabilities.MotionSensor);
}
+ public bool IsPhysical()
+ {
+ return !IsVirtual();
+ }
+
public bool IsVirtual()
{
if (Details is not null)
@@ -141,25 +201,22 @@ public override string ToString()
return string.Empty;
}
- protected void DrawControls()
+ protected void DrawUI()
{
// update name
- ui_name.Text = (IsVirtual() ? Properties.Resources.Controller_Virtual : string.Empty) + ToString();
+ ControllerName.Text = (IsVirtual() ? Properties.Resources.Controller_Virtual : string.Empty) + ToString();
// virtual controller shouldn't be visible
if (IsVirtual())
this.Visibility = Visibility.Collapsed;
}
- protected void RefreshControls()
+ protected void UpdateUI()
{
// UI thread (async)
Application.Current.Dispatcher.BeginInvoke(() =>
{
- if (!IsEnabled)
- return;
-
- ui_button_hook.Content = IsPlugged() ? Properties.Resources.Controller_Disconnect : Properties.Resources.Controller_Connect;
+ // ui_button_hook.Content = IsPlugged ? Properties.Resources.Controller_Disconnect : Properties.Resources.Controller_Connect;
ui_button_hide.Content = IsHidden() ? Properties.Resources.Controller_Unhide : Properties.Resources.Controller_Hide;
ui_button_calibrate.Visibility = Capabilities.HasFlag(ControllerCapabilities.Calibration) ? Visibility.Visible : Visibility.Collapsed;
});
@@ -215,46 +272,86 @@ public virtual void SetVibration(byte LargeMotor, byte SmallMotor)
// let the controller decide itself what motor to use for a specific button
public virtual void SetHaptic(HapticStrength strength, ButtonFlags button)
- { }
+ {
+ int delay;
+ switch (strength)
+ {
+ default:
+ case HapticStrength.Low:
+ delay = 85;
+ break;
+
+ case HapticStrength.Medium:
+ delay = 105;
+ break;
+
+ case HapticStrength.High:
+ delay = 125;
+ break;
+ }
+
+ switch (button)
+ {
+ case ButtonFlags.B1:
+ case ButtonFlags.B2:
+ case ButtonFlags.B3:
+ case ButtonFlags.B4:
+ case ButtonFlags.L1:
+ case ButtonFlags.L2Soft:
+ case ButtonFlags.Start:
+ case ButtonFlags.RightStickClick:
+ case ButtonFlags.RightPadClick:
+ Rumble(delay, 0, byte.MaxValue);
+ break;
+ default:
+ Rumble(delay, byte.MaxValue, 0);
+ break;
+ }
+ }
public virtual bool IsConnected()
{
return false;
}
- public virtual void Rumble(int delay = 125)
+ private Task rumbleTask;
+ public virtual void Rumble(int delay = 125, byte LargeMotor = byte.MaxValue, byte SmallMotor = byte.MaxValue)
{
- Task.Run(async () =>
+ // If the current task is not null and not completed
+ if (rumbleTask != null && !rumbleTask.IsCompleted)
+ SetVibration(0, 0);
+
+ // Create a new task that executes the following code
+ rumbleTask = Task.Run(async () =>
{
- SetVibration(byte.MaxValue, byte.MaxValue);
+ SetVibration(LargeMotor, SmallMotor);
await Task.Delay(delay);
SetVibration(0, 0);
});
}
- public virtual bool IsPlugged()
- {
- return isPlugged;
- }
-
// this function cannot be called twice
public virtual void Plug()
{
SetVibrationStrength(SettingsManager.GetUInt("VibrationStrength"));
- isPlugged = true;
-
InjectedButtons.Clear();
- RefreshControls();
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ ui_button_hook.IsEnabled = false;
+ });
}
// this function cannot be called twice
public virtual void Unplug()
{
- isPlugged = false;
-
- RefreshControls();
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ ui_button_hook.IsEnabled = true;
+ });
}
// like Unplug but one that can be safely called when controller is already removed
@@ -264,8 +361,8 @@ public virtual void Cleanup()
public bool IsHidden()
{
- // var hide_device = HidHide.IsRegistered(Details.deviceInstanceId);
- var hide_base = HidHide.IsRegistered(Details.baseContainerDeviceInstanceId);
+ // bool hide_device = HidHide.IsRegistered(Details.deviceInstanceId);
+ bool hide_base = HidHide.IsRegistered(Details.baseContainerDeviceInstanceId);
return /* hide_device || */ hide_base;
}
@@ -275,20 +372,13 @@ public virtual void Hide(bool powerCycle = true)
if (powerCycle)
{
- // UI thread (async)
- Application.Current.Dispatcher.BeginInvoke(() =>
- {
- IsEnabled = false;
- ProgressBarPanel.Visibility = Visibility.Visible;
- });
+ IsBusy = true;
ControllerManager.PowerCyclers[Details.baseContainerDeviceInstanceId] = true;
-
CyclePort();
- return;
}
- RefreshControls();
+ UpdateUI();
}
public virtual void Unhide(bool powerCycle = true)
@@ -297,20 +387,13 @@ public virtual void Unhide(bool powerCycle = true)
if (powerCycle)
{
- // UI thread (async)
- Application.Current.Dispatcher.BeginInvoke(() =>
- {
- IsEnabled = false;
- ProgressBarPanel.Visibility = Visibility.Visible;
- });
+ IsBusy = true;
ControllerManager.PowerCyclers[Details.baseContainerDeviceInstanceId] = true;
-
CyclePort();
- return;
}
- RefreshControls();
+ UpdateUI();
}
public virtual void CyclePort()
@@ -322,7 +405,7 @@ public virtual void SetLightColor(byte R, byte G, byte B)
{
}
- public void HideHID()
+ protected void HideHID()
{
HidHide.HidePath(Details.baseContainerDeviceInstanceId);
HidHide.HidePath(Details.deviceInstanceId);
@@ -336,7 +419,7 @@ public void HideHID()
*/
}
- public void UnhideHID()
+ protected void UnhideHID()
{
HidHide.UnhidePath(Details.baseContainerDeviceInstanceId);
HidHide.UnhidePath(Details.deviceInstanceId);
@@ -406,26 +489,6 @@ public virtual string GetGlyph(ButtonFlags button)
return "\u21BD";
case ButtonFlags.RightStickRight:
return "\u21C1";
- case ButtonFlags.OEM1:
- return "\u2780";
- case ButtonFlags.OEM2:
- return "\u2781";
- case ButtonFlags.OEM3:
- return "\u2782";
- case ButtonFlags.OEM4:
- return "\u2783";
- case ButtonFlags.OEM5:
- return "\u2784";
- case ButtonFlags.OEM6:
- return "\u2785";
- case ButtonFlags.OEM7:
- return "\u2786";
- case ButtonFlags.OEM8:
- return "\u2787";
- case ButtonFlags.OEM9:
- return "\u2788";
- case ButtonFlags.OEM10:
- return "\u2789";
case ButtonFlags.VolumeUp:
return "\u21fe";
case ButtonFlags.VolumeDown:
@@ -567,6 +630,9 @@ public string GetAxisName(AxisLayoutFlags axis)
#region events
+ public event UserIndexChangedEventHandler UserIndexChanged;
+ public delegate void UserIndexChangedEventHandler(byte UserIndex);
+
public event InputsUpdatedEventHandler InputsUpdated;
public delegate void InputsUpdatedEventHandler(ControllerState Inputs);
diff --git a/HandheldCompanion/Controllers/JSController.cs b/HandheldCompanion/Controllers/JSController.cs
index cab491d15..de0bd42d8 100644
--- a/HandheldCompanion/Controllers/JSController.cs
+++ b/HandheldCompanion/Controllers/JSController.cs
@@ -1,6 +1,7 @@
using HandheldCompanion.Inputs;
using HandheldCompanion.Utils;
using Nefarius.Utilities.DeviceManagement.PnP;
+using System;
using System.Threading.Tasks;
using System.Windows;
using static JSL;
@@ -26,17 +27,8 @@ public JSController()
public JSController(JOY_SETTINGS settings, PnPDetails details)
{
- if (string.IsNullOrEmpty(settings.path))
- return;
-
- this.sSETTINGS = settings;
- this.UserIndex = settings.playerNumber;
-
- if (details is null)
- return;
-
- Details = details;
- Details.isHooked = true;
+ AttachJoySettings(settings);
+ AttachDetails(details);
// timer(s)
calibrateTimer.Elapsed += CalibrateTimer_Elapsed;
@@ -46,11 +38,8 @@ public JSController(JOY_SETTINGS settings, PnPDetails details)
Capabilities |= ControllerCapabilities.Calibration;
// UI
- InitializeComponent();
- DrawControls();
- RefreshControls();
-
- JslSetAutomaticCalibration(UserIndex, true);
+ DrawUI();
+ UpdateUI();
}
public override string ToString()
@@ -223,4 +212,12 @@ private void CalibrateTimer_Elapsed(object? sender, System.Timers.ElapsedEventAr
ui_button_calibrate.IsEnabled = true;
});
}
+
+ public void AttachJoySettings(JOY_SETTINGS settings)
+ {
+ this.sSETTINGS = settings;
+ this.UserIndex = (byte)settings.playerNumber;
+
+ JslSetAutomaticCalibration(UserIndex, true);
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Controllers/LegionController.cs b/HandheldCompanion/Controllers/LegionController.cs
new file mode 100644
index 000000000..c904d51f7
--- /dev/null
+++ b/HandheldCompanion/Controllers/LegionController.cs
@@ -0,0 +1,386 @@
+using HandheldCompanion.Devices;
+using HandheldCompanion.Inputs;
+using HandheldCompanion.Utils;
+using HidLibrary;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Windows.Forms;
+using static HandheldCompanion.Devices.Lenovo.SapientiaUsb;
+
+namespace HandheldCompanion.Controllers
+{
+ public class LegionController : XInputController
+ {
+ // Import the user32.dll library
+ [DllImport("user32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
+ static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref uint pvParam, uint fWinIni);
+
+ [Flags]
+ private enum FrontEnum
+ {
+ None = 0,
+ LegionR = 64,
+ LegionL = 128,
+ }
+
+ [Flags]
+ private enum BackEnum
+ {
+ None = 0,
+ M3 = 4,
+ M2 = 8,
+ Y3 = 32,
+ Y2 = 64,
+ Y1 = 128,
+ }
+
+ private HidDevice hidDevice;
+ private const byte FRONT_IDX = 17;
+ private const byte BACK_IDX = 19;
+ private const byte STATUS_IDX = 0;
+ private const byte PING_IDX = 40;
+
+ private Thread dataThread;
+ private bool dataThreadRunning;
+
+ private byte[] Data = new byte[64];
+ public override bool IsReady
+ {
+ get
+ {
+ byte status = GetStatus(STATUS_IDX);
+ return status == 25;
+ }
+ }
+
+ public bool IsWireless
+ {
+ get
+ {
+ byte status = GetStatus(PING_IDX);
+ return (status >= 40 && status <= 50);
+ }
+ }
+
+ // Define some constants for the touchpad logic
+ private bool IsPassthrough = false;
+ private uint LongPressTime = 1000; // The minimum time in milliseconds for a long press
+ private const int MaxDistance = 40; // Maximum distance tolerance between touch and untouch in pixels
+
+ // Variables to store the touchpad state
+ private bool touchpadTouched = false; // Whether the touchpad is currently touched
+ private Vector2 touchpadPosition = Vector2.Zero; // The current position of the touchpad
+ private Vector2 touchpadFirstPosition = Vector2.Zero; // The first position of the touchpad when touched
+ private long touchpadStartTime = 0; // The start time of the touchpad when touched
+ private long touchpadEndTime = 0; // The end time of the touchpad when untouched
+ private bool touchpadDoubleTapped = false; // Whether the touchpad has been double tapped
+ private bool touchpadLongTapped = false; // Whether the touchpad has been long tapped
+
+ private long lastTap = 0;
+ private Vector2 lastTapPosition = Vector2.Zero; // The current position of the touchpad
+
+ public LegionController(PnPDetails details) : base(details)
+ {
+ // Additional controller specific source buttons
+ SourceButtons.Add(ButtonFlags.RightPadTouch);
+ SourceButtons.Add(ButtonFlags.RightPadClick);
+ SourceButtons.Add(ButtonFlags.RightPadClickDown);
+
+ SourceButtons.Add(ButtonFlags.R4);
+ SourceButtons.Add(ButtonFlags.R5);
+ SourceButtons.Add(ButtonFlags.L4);
+ SourceButtons.Add(ButtonFlags.L5);
+
+ SourceButtons.Add(ButtonFlags.B5);
+ SourceButtons.Add(ButtonFlags.B6);
+ SourceButtons.Add(ButtonFlags.B7);
+ SourceButtons.Add(ButtonFlags.B8);
+
+ SourceAxis.Add(AxisLayoutFlags.RightPad);
+ SourceAxis.Add(AxisLayoutFlags.Gyroscope);
+
+ // get long press time from system settings
+ SystemParametersInfo(0x006A, 0, ref LongPressTime, 0);
+ }
+
+ public override void AttachDetails(PnPDetails details)
+ {
+ base.AttachDetails(details);
+
+ hidDevice = GetHidDevice();
+ if (hidDevice is not null)
+ hidDevice.OpenDevice();
+ }
+
+ private HidDevice GetHidDevice()
+ {
+ IEnumerable devices = IDevice.GetHidDevices(Details.VendorID, Details.ProductID, 0);
+ foreach (HidDevice device in devices)
+ {
+ if (!device.IsConnected)
+ continue;
+
+ if (device.Capabilities.InputReportByteLength == 64)
+ return device; // HID-compliant vendor-defined device
+ }
+
+ return null;
+ }
+
+ private byte GetStatus(int idx)
+ {
+ if (hidDevice is not null)
+ {
+ HidReport report = hidDevice.ReadReport();
+ if (report.Data is not null)
+ return report.Data[idx];
+ }
+
+ return 0;
+ }
+
+ public override void Plug()
+ {
+ hidDevice = GetHidDevice();
+
+ if (hidDevice is not null && hidDevice.IsConnected)
+ {
+ if (!hidDevice.IsOpen)
+ hidDevice.OpenDevice();
+
+ dataThreadRunning = true;
+ dataThread = new Thread(dataThreadLoop);
+ dataThread.IsBackground = true;
+ dataThread.Start();
+ }
+
+ base.Plug();
+ }
+
+ public override void Unplug()
+ {
+ if (hidDevice is not null)
+ {
+ // kill rumble thread
+ dataThreadRunning = false;
+ if (dataThread is not null)
+ dataThread.Join();
+
+ if (hidDevice.IsConnected && hidDevice.IsOpen)
+ {
+ hidDevice.CloseDevice();
+ hidDevice.Dispose();
+ hidDevice = null;
+ }
+ }
+
+ base.Unplug();
+ }
+
+ public override void UpdateInputs(long ticks, bool commit)
+ {
+ // skip if controller isn't connected
+ if (!IsConnected())
+ return;
+
+ base.UpdateInputs(ticks, false);
+
+ FrontEnum frontButton = (FrontEnum)Data[FRONT_IDX];
+ Inputs.ButtonState[ButtonFlags.OEM1] = frontButton.HasFlag(FrontEnum.LegionR);
+ Inputs.ButtonState[ButtonFlags.OEM2] = frontButton.HasFlag(FrontEnum.LegionL);
+
+ BackEnum backButton = (BackEnum)Data[BACK_IDX];
+ Inputs.ButtonState[ButtonFlags.R4] = backButton.HasFlag(BackEnum.M3);
+ Inputs.ButtonState[ButtonFlags.R5] = backButton.HasFlag(BackEnum.Y3);
+ Inputs.ButtonState[ButtonFlags.L4] = backButton.HasFlag(BackEnum.Y1);
+ Inputs.ButtonState[ButtonFlags.L5] = backButton.HasFlag(BackEnum.Y2);
+ Inputs.ButtonState[ButtonFlags.B5] = backButton.HasFlag(BackEnum.M2);
+ Inputs.ButtonState[ButtonFlags.B6] = Data[20] == 128; // Scroll click
+ Inputs.ButtonState[ButtonFlags.B7] = Data[24] == 129; // Scroll up
+ Inputs.ButtonState[ButtonFlags.B8] = Data[24] == 255; // Scroll down
+
+ // Right Pad
+ ushort TouchpadX = (ushort)((Data[25] << 8) | Data[26]);
+ ushort TouchpadY = (ushort)((Data[27] << 8) | Data[28]);
+
+ bool touched = (TouchpadX != 0 || TouchpadY != 0);
+
+ Inputs.ButtonState[ButtonFlags.RightPadTouch] = touched;
+ Inputs.ButtonState[ButtonFlags.RightPadClick] = false;
+ Inputs.ButtonState[ButtonFlags.RightPadClickDown] = false;
+
+ // handle touchpad if passthrough is off
+ if (!IsPassthrough)
+ HandleTouchpadInput(touched, TouchpadX, TouchpadY);
+
+ /*
+ Inputs.AxisState[AxisFlags.LeftStickX] += (short)InputUtils.MapRange(Data[29], byte.MinValue, byte.MaxValue, short.MinValue, short.MaxValue);
+ Inputs.AxisState[AxisFlags.LeftStickY] -= (short)InputUtils.MapRange(Data[30], byte.MinValue, byte.MaxValue, short.MinValue, short.MaxValue);
+
+ Inputs.AxisState[AxisFlags.RightStickX] += (short)InputUtils.MapRange(Data[31], byte.MinValue, byte.MaxValue, short.MinValue, short.MaxValue);
+ Inputs.AxisState[AxisFlags.RightStickY] -= (short)InputUtils.MapRange(Data[32], byte.MinValue, byte.MaxValue, short.MinValue, short.MaxValue);
+ */
+
+ base.UpdateInputs(ticks);
+ }
+
+ private async void dataThreadLoop(object? obj)
+ {
+ // pull latest Data
+ while (dataThreadRunning)
+ {
+ if (hidDevice is null)
+ continue;
+
+ HidReport report = hidDevice.ReadReport();
+ if (report is not null)
+ {
+ // check if packet is safe
+ if (report.Data[STATUS_IDX] == 25)
+ Data = report.Data;
+ }
+ }
+ }
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.B5:
+ return "\u2213"; // M2
+ case ButtonFlags.B6:
+ return "\u2206"; // Scroll click
+ case ButtonFlags.B7:
+ return "\u27F0"; // Scroll up
+ case ButtonFlags.B8:
+ return "\u27F1"; // Scroll down
+ }
+
+ return base.GetGlyph(button);
+ }
+
+ public void HandleTouchpadInput(bool touched, ushort x, ushort y)
+ {
+ // Convert the ushort values to Vector2
+ Vector2 position = new Vector2(x, y);
+
+ // If the touchpad is touched
+ if (touched)
+ {
+ Inputs.AxisState[AxisFlags.RightPadX] = (short)InputUtils.MapRange((short)x, 0, 1000, short.MinValue, short.MaxValue);
+ Inputs.AxisState[AxisFlags.RightPadY] = (short)InputUtils.MapRange((short)-y, 0, 1000, short.MinValue, short.MaxValue);
+
+ // If the touchpad was not touched before
+ if (!touchpadTouched)
+ {
+ // Set the touchpad state variables
+ touchpadTouched = true;
+ touchpadPosition = position;
+ touchpadFirstPosition = position;
+ touchpadStartTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
+
+ // Set the right pad touch flag to true
+ Inputs.ButtonState[ButtonFlags.RightPadTouch] = true;
+
+ long delay = touchpadStartTime - lastTap;
+ float distance = Vector2.Distance(touchpadFirstPosition, lastTapPosition);
+
+ if (delay < SystemInformation.DoubleClickTime && distance < MaxDistance * 5)
+ {
+ Inputs.ButtonState[ButtonFlags.RightPadClick] = true;
+ touchpadDoubleTapped = true;
+ }
+ }
+ // If the touchpad was touched before
+ else
+ {
+ // Update the touchpad position
+ touchpadPosition = position;
+
+ // If the touchpad has been double tapped
+ if (touchpadDoubleTapped)
+ {
+ // Keep the right pad click flag to true
+ Inputs.ButtonState[ButtonFlags.RightPadClick] = true;
+ }
+ else
+ {
+ // Calculate the duration and the distance of the touchpad
+ long duration = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond - touchpadStartTime;
+ float distance = Vector2.Distance(touchpadFirstPosition, touchpadPosition);
+
+ // If the duration is more than the long tap duration and the distance is less than the maximum distance
+ if (duration >= LongPressTime && duration < (LongPressTime + 100) && distance < MaxDistance)
+ {
+ // If the touchpad has not been long tapped before
+ if (!touchpadLongTapped)
+ {
+ // Set the right pad click down flag to true
+ Inputs.ButtonState[ButtonFlags.RightPadClickDown] = true;
+
+ // Set the touchpad long tapped flag to true
+ touchpadLongTapped = true;
+ }
+ }
+ }
+ }
+ }
+ // If the touchpad is not touched
+ else
+ {
+ Inputs.AxisState[AxisFlags.RightPadX] = 0;
+ Inputs.AxisState[AxisFlags.RightPadY] = 0;
+
+ // If the touchpad was touched before
+ if (touchpadTouched)
+ {
+ // Set the touchpad state variables
+ touchpadTouched = false;
+ touchpadEndTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
+
+ // Set the right pad touch flag to false
+ Inputs.ButtonState[ButtonFlags.RightPadTouch] = false;
+
+ // Calculate the duration and the distance of the touchpad
+ long duration = touchpadEndTime - touchpadStartTime;
+ float distance = Vector2.Distance(touchpadFirstPosition, touchpadPosition);
+
+ // If the duration is less than the short tap duration and the distance is less than the maximum distance
+ if (duration < SystemInformation.DoubleClickTime && distance < MaxDistance)
+ {
+ // Set the right pad click flag to true
+ Inputs.ButtonState[ButtonFlags.RightPadClick] = true;
+
+ // Store tap time
+ lastTap = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
+ lastTapPosition = touchpadPosition;
+ }
+
+ // Set the touchpad long tapped flag to false
+ touchpadLongTapped = false;
+
+ // Set the touchpad double tapped flag to false
+ touchpadDoubleTapped = false;
+ }
+ }
+ }
+
+ internal void SetPassthrough(bool enabled)
+ {
+ switch(enabled)
+ {
+ case true:
+ SetTouchPadStatus(1);
+ break;
+ case false:
+ SetTouchPadStatus(0);
+ break;
+ }
+
+ IsPassthrough = enabled;
+ }
+ }
+}
diff --git a/HandheldCompanion/Controllers/NeptuneController.cs b/HandheldCompanion/Controllers/NeptuneController.cs
index dd9a7ab6a..0438e5a77 100644
--- a/HandheldCompanion/Controllers/NeptuneController.cs
+++ b/HandheldCompanion/Controllers/NeptuneController.cs
@@ -30,29 +30,11 @@ public class NeptuneController : SteamController
public NeptuneController(PnPDetails details) : base()
{
- if (details is null)
- return;
-
- Details = details;
- Details.isHooked = true;
-
- try
- {
- Controller = new(details.attributes.VendorID, details.attributes.ProductID, details.GetMI());
-
- // open controller
- Open();
- }
- catch (Exception ex)
- {
- LogManager.LogError("Couldn't initialize NeptuneController. Exception: {0}", ex.Message);
- return;
- }
+ AttachDetails(details);
// UI
- InitializeComponent();
- DrawControls();
- RefreshControls();
+ DrawUI();
+ UpdateUI();
// Additional controller specific source buttons/axes
SourceButtons.AddRange(new List
@@ -82,12 +64,19 @@ public NeptuneController(PnPDetails details) : base()
TargetAxis.Add(AxisLayoutFlags.RightPad);
}
+ public override void AttachDetails(PnPDetails details)
+ {
+ base.AttachDetails(details);
+
+ Controller = new(details.VendorID, details.ProductID, details.GetMI());
+
+ // open controller
+ Open();
+ }
+
public override string ToString()
{
- var baseName = base.ToString();
- if (!string.IsNullOrEmpty(baseName))
- return baseName;
- return "Steam Controller Neptune";
+ return "Valve Software Steam Controller";
}
public override void UpdateInputs(long ticks)
@@ -277,6 +266,22 @@ private void Close()
catch { }
}
+ public override void Hide(bool powerCycle = true)
+ {
+ Close();
+ base.Hide(powerCycle);
+ if (!powerCycle)
+ Open();
+ }
+
+ public override void Unhide(bool powerCycle = true)
+ {
+ Close();
+ base.Unhide(powerCycle);
+ if (!powerCycle)
+ Open();
+ }
+
private void OnControllerInputReceived(NeptuneControllerInputEventArgs input)
{
this.input = input;
diff --git a/HandheldCompanion/Controllers/ProController.cs b/HandheldCompanion/Controllers/ProController.cs
index 7e3dda98b..170232f27 100644
--- a/HandheldCompanion/Controllers/ProController.cs
+++ b/HandheldCompanion/Controllers/ProController.cs
@@ -31,6 +31,11 @@ public override void UpdateInputs(long ticks)
base.UpdateInputs(ticks);
}
+ public override string ToString()
+ {
+ return "Nintendo Pro Controller";
+ }
+
public override void Plug()
{
TimerManager.Tick += UpdateInputs;
diff --git a/HandheldCompanion/Controllers/SteamController.cs b/HandheldCompanion/Controllers/SteamController.cs
index d429b2882..9dbb16aa5 100644
--- a/HandheldCompanion/Controllers/SteamController.cs
+++ b/HandheldCompanion/Controllers/SteamController.cs
@@ -28,20 +28,6 @@ public virtual void SetVirtualMuted(bool mute)
isVirtualMuted = mute;
}
- public override void Hide(bool powerCycle = true)
- {
- HideHID();
-
- RefreshControls();
- }
-
- public override void Unhide(bool powerCycle = true)
- {
- UnhideHID();
-
- RefreshControls();
- }
-
public override string GetGlyph(ButtonFlags button)
{
switch (button)
@@ -69,13 +55,13 @@ public override string GetGlyph(ButtonFlags button)
case ButtonFlags.R2Full:
return "\u21B3";
case ButtonFlags.L4:
- return "\u219c\u24f8";
+ return "\u2276";
case ButtonFlags.L5:
- return "\u219c\u24f9";
+ return "\u2278";
case ButtonFlags.R4:
- return "\u219d\u24f8";
+ return "\u2277";
case ButtonFlags.R5:
- return "\u219d\u24f9";
+ return "\u2279";
case ButtonFlags.Special:
return "\u21E4";
case ButtonFlags.OEM1:
diff --git a/HandheldCompanion/Controllers/XInputController.cs b/HandheldCompanion/Controllers/XInputController.cs
index 9dcec7290..4e06b6faf 100644
--- a/HandheldCompanion/Controllers/XInputController.cs
+++ b/HandheldCompanion/Controllers/XInputController.cs
@@ -1,12 +1,10 @@
using HandheldCompanion.Inputs;
using HandheldCompanion.Managers;
-using Nefarius.Utilities.DeviceManagement.PnP;
using SharpDX.XInput;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
-using System.Windows;
using System.Windows.Media;
namespace HandheldCompanion.Controllers;
@@ -19,22 +17,12 @@ public class XInputController : IController
private XInputStateSecret State;
public XInputController()
- {
- }
+ { }
- public XInputController(Controller controller, PnPDetails details) : this()
+ public XInputController(PnPDetails details)
{
- Controller = controller;
- UserIndex = (int)controller.UserIndex;
-
- if (!IsConnected())
- return;
-
- this.Details = details;
- if (Details is null)
- return;
-
- Details.isHooked = true;
+ AttachController(details.XInputUserIndex);
+ AttachDetails(details);
// UI
ColoredButtons.Add(ButtonFlags.B1, new SolidColorBrush(Color.FromArgb(255, 81, 191, 61)));
@@ -42,15 +30,20 @@ public XInputController(Controller controller, PnPDetails details) : this()
ColoredButtons.Add(ButtonFlags.B3, new SolidColorBrush(Color.FromArgb(255, 26, 159, 255)));
ColoredButtons.Add(ButtonFlags.B4, new SolidColorBrush(Color.FromArgb(255, 255, 200, 44)));
- InitializeComponent();
- DrawControls();
- RefreshControls();
- }
+ DrawUI();
+ UpdateUI();
- public void UpdateController(Controller controller)
- {
- Controller = controller;
- UserIndex = (int)controller.UserIndex;
+ string enumerator = Details.GetEnumerator();
+ switch (enumerator)
+ {
+ default:
+ case "BTHENUM":
+ ProgressBarWarning.Text = Properties.Resources.XInputController_Warning_BTH;
+ break;
+ case "USB":
+ ProgressBarWarning.Text = Properties.Resources.XInputController_Warning_USB;
+ break;
+ }
}
public override string ToString()
@@ -58,10 +51,10 @@ public override string ToString()
var baseName = base.ToString();
if (!string.IsNullOrEmpty(baseName))
return baseName;
- return $"XInput Controller {UserIndex}";
+ return $"XInput Controller {(UserIndex)UserIndex}";
}
- public override void UpdateInputs(long ticks)
+ public virtual void UpdateInputs(long ticks, bool commit)
{
// skip if controller isn't connected
if (!IsConnected())
@@ -127,7 +120,8 @@ public override void UpdateInputs(long ticks)
}
catch { }
- base.UpdateInputs(ticks);
+ if (commit)
+ base.UpdateInputs(ticks);
}
public override bool IsConnected()
@@ -142,22 +136,26 @@ public override void SetVibration(byte LargeMotor, byte SmallMotor)
if (!IsConnected())
return;
- var LeftMotorSpeed = (ushort)((double)LargeMotor / byte.MaxValue * ushort.MaxValue * VibrationStrength);
- var RightMotorSpeed = (ushort)((double)SmallMotor / byte.MaxValue * ushort.MaxValue * VibrationStrength);
+ try
+ {
+ ushort LeftMotorSpeed = (ushort)((double)LargeMotor / byte.MaxValue * ushort.MaxValue * VibrationStrength);
+ ushort RightMotorSpeed = (ushort)((double)SmallMotor / byte.MaxValue * ushort.MaxValue * VibrationStrength);
- var vibration = new Vibration { LeftMotorSpeed = LeftMotorSpeed, RightMotorSpeed = RightMotorSpeed };
- Controller.SetVibration(vibration);
+ Vibration vibration = new Vibration { LeftMotorSpeed = LeftMotorSpeed, RightMotorSpeed = RightMotorSpeed };
+ Controller.SetVibration(vibration);
+ }
+ catch { }
}
public override void Plug()
{
- TimerManager.Tick += UpdateInputs;
+ TimerManager.Tick += (ticks) => UpdateInputs(ticks, true);
base.Plug();
}
public override void Unplug()
{
- TimerManager.Tick -= UpdateInputs;
+ TimerManager.Tick -= (ticks) => UpdateInputs(ticks, true);
base.Unplug();
}
@@ -174,7 +172,7 @@ public static UserIndex TryGetUserIndex(PnPDetails details)
{
if (XInputGetCapabilitiesEx(1, idx, 0, ref capabilitiesEx) == 0)
{
- if (capabilitiesEx.ProductId != details.attributes.ProductID || capabilitiesEx.VendorId != details.attributes.VendorID)
+ if (capabilitiesEx.ProductId != details.ProductID || capabilitiesEx.VendorId != details.VendorID)
continue;
var devices = DeviceManager.GetDetails(capabilitiesEx.VendorId, capabilitiesEx.ProductId);
@@ -186,31 +184,22 @@ public static UserIndex TryGetUserIndex(PnPDetails details)
return SharpDX.XInput.UserIndex.Any;
}
- public override void Hide(bool powerCycle = true)
+ public virtual void AttachController(byte userIndex)
{
- if (powerCycle)
- {
- // UI thread (async)
- Application.Current.Dispatcher.BeginInvoke(() =>
- {
- ProgressBarWarning.Text = Properties.Resources.ControllerPage_XInputControllerWarning;
- });
- }
+ if (UserIndex == userIndex)
+ return;
+
+ UserIndex = userIndex;
+ Controller = new((UserIndex)userIndex);
+ }
+ public override void Hide(bool powerCycle = true)
+ {
base.Hide(powerCycle);
}
public override void Unhide(bool powerCycle = true)
{
- if (powerCycle)
- {
- // UI thread (async)
- Application.Current.Dispatcher.BeginInvoke(() =>
- {
- ProgressBarWarning.Text = Properties.Resources.ControllerPage_XInputControllerWarning;
- });
- }
-
base.Unhide(powerCycle);
}
diff --git a/HandheldCompanion/Controls/Hints/Hint_CoreIsolationCheck.cs b/HandheldCompanion/Controls/Hints/Hint_CoreIsolationCheck.cs
new file mode 100644
index 000000000..54537025b
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/Hint_CoreIsolationCheck.cs
@@ -0,0 +1,108 @@
+using HandheldCompanion.Misc;
+using HandheldCompanion.Utils;
+using Inkore.UI.WPF.Modern.Controls;
+using System;
+using System.Diagnostics;
+using System.Management;
+using System.Windows;
+
+namespace HandheldCompanion.Controls.Hints
+{
+ public class Hint_CoreIsolationCheck : IHint
+ {
+ private static WqlEventQuery HypervisorQuery = new WqlEventQuery(@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_LOCAL_MACHINE' AND KeyPath = 'SYSTEM\\CurrentControlSet\\Control\\DeviceGuard\\Scenarios' AND ValueName='HypervisorEnforcedCodeIntegrity'");
+ private static WqlEventQuery VulnerableDriverQuery = new WqlEventQuery(@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_LOCAL_MACHINE' AND KeyPath = 'SYSTEM\\CurrentControlSet\\Control\\CI\\Config' AND ValueName='VulnerableDriverBlocklistEnable'");
+
+ private ManagementEventWatcher VulnerableDriverWatcher = new ManagementEventWatcher(VulnerableDriverQuery);
+ private ManagementEventWatcher HypervisorWatcher = new ManagementEventWatcher(HypervisorQuery);
+
+ bool HypervisorEnforcedCodeIntegrityEnabled = true;
+ bool VulnerableDriverBlocklistEnable = true;
+
+ public Hint_CoreIsolationCheck() : base()
+ {
+ if (RegistryUtils.KeyExists(@"SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios", "HypervisorEnforcedCodeIntegrity"))
+ {
+ HypervisorWatcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
+ HypervisorWatcher.Start();
+ }
+
+ if (RegistryUtils.KeyExists(@"SYSTEM\CurrentControlSet\Control\CI\Config", "VulnerableDriverBlocklistEnable"))
+ {
+ VulnerableDriverWatcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
+ VulnerableDriverWatcher.Start();
+ }
+
+ // default state
+ this.HintActionButton.Visibility = Visibility.Visible;
+
+ this.HintTitle.Text = Properties.Resources.Hint_CoreIsolationCheck;
+ this.HintDescription.Text = Properties.Resources.Hint_CoreIsolationCheckDesc;
+ this.HintReadMe.Text = Properties.Resources.Hint_CoreIsolationCheckReadme;
+
+ this.HintActionButton.Content = Properties.Resources.Hint_CoreIsolationCheckAction;
+
+ CheckSettings();
+ }
+
+ private void HandleEvent(object sender, EventArrivedEventArgs e)
+ {
+ CheckSettings();
+ }
+
+ private void CheckSettings()
+ {
+ // read OS specific values
+ HypervisorEnforcedCodeIntegrityEnabled = RegistryUtils.GetBoolean(@"SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios", "HypervisorEnforcedCodeIntegrity");
+ VulnerableDriverBlocklistEnable = RegistryUtils.GetBoolean(@"SYSTEM\CurrentControlSet\Control\CI\Config", "VulnerableDriverBlocklistEnable");
+
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ this.Visibility = HypervisorEnforcedCodeIntegrityEnabled || VulnerableDriverBlocklistEnable ? Visibility.Visible : Visibility.Collapsed;
+ });
+ }
+
+ protected override async void HintActionButton_Click(object sender, RoutedEventArgs e)
+ {
+ RegistryUtils.SetValue(@"SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios", "HypervisorEnforcedCodeIntegrity", 0);
+ RegistryUtils.SetValue(@"SYSTEM\CurrentControlSet\Control\CI\Config", "VulnerableDriverBlocklistEnable", 0);
+
+ var result = Dialog.ShowAsync($"{Properties.Resources.Dialog_ForceRestartTitle}",
+ $"{Properties.Resources.Dialog_ForceRestartDesc}",
+ ContentDialogButton.Primary, null,
+ $"{Properties.Resources.Dialog_Yes}",
+ $"{Properties.Resources.Dialog_No}");
+
+ await result;
+
+ switch (result.Result)
+ {
+ case ContentDialogResult.Primary:
+ using (Process shutdown = new())
+ {
+ shutdown.StartInfo.FileName = "shutdown.exe";
+ shutdown.StartInfo.Arguments = "-r -t 3";
+
+ shutdown.StartInfo.UseShellExecute = false;
+ shutdown.StartInfo.CreateNoWindow = true;
+ shutdown.Start();
+ }
+ break;
+ case ContentDialogResult.Secondary:
+ break;
+ }
+ }
+
+ public override void Stop()
+ {
+ if (RegistryUtils.KeyExists(@"SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios", "HypervisorEnforcedCodeIntegrity"))
+ HypervisorWatcher.Stop();
+
+ if (RegistryUtils.KeyExists(@"SYSTEM\CurrentControlSet\Control\CI\Config", "VulnerableDriverBlocklistEnable"))
+ VulnerableDriverWatcher.Stop();
+
+ base.Stop();
+ }
+ }
+}
diff --git a/HandheldCompanion/Controls/Hints/Hint_HWiNFO12hLimitPassed.cs b/HandheldCompanion/Controls/Hints/Hint_HWiNFO12hLimitPassed.cs
new file mode 100644
index 000000000..f91a4c092
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/Hint_HWiNFO12hLimitPassed.cs
@@ -0,0 +1,60 @@
+using HandheldCompanion.Managers;
+using HandheldCompanion.Platforms;
+using System;
+using System.Windows;
+
+namespace HandheldCompanion.Controls.Hints
+{
+ public class Hint_HWiNFO12hLimitPassed : IHint
+ {
+ public Hint_HWiNFO12hLimitPassed() : base()
+ {
+ PlatformManager.HWiNFO.Updated += HWiNFO_Updated;
+ PlatformManager.HWiNFO.SettingValueChanged += HWiNFO_SettingValueChanged;
+
+ PlatformManager.Initialized += PlatformManager_Initialized;
+
+ // default state
+ this.HintActionButton.Visibility = Visibility.Collapsed;
+
+ this.HintTitle.Text = Properties.Resources.Hint_HWiNFO12hLimitPassed;
+ this.HintDescription.Text = Properties.Resources.Hint_HWiNFO12hLimitPassedDesc;
+ this.HintReadMe.Text = Properties.Resources.Hint_HWiNFO12hLimitPassedReadme;
+ }
+
+ private void HWiNFO_Updated(PlatformStatus status)
+ {
+ CheckSettings();
+ }
+
+ private void HWiNFO_SettingValueChanged(string name, object value)
+ {
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ switch(name)
+ {
+ case "SensorsSM":
+ this.Visibility = Convert.ToBoolean(value) ? Visibility.Collapsed : Visibility.Visible;
+ break;
+ }
+ });
+ }
+
+ private void PlatformManager_Initialized()
+ {
+ CheckSettings();
+ }
+
+ private void CheckSettings()
+ {
+ bool SensorsSM = PlatformManager.HWiNFO.GetProperty("SensorsSM");
+ HWiNFO_SettingValueChanged("SensorsSM", SensorsSM);
+ }
+
+ public override void Stop()
+ {
+ base.Stop();
+ }
+ }
+}
diff --git a/HandheldCompanion/Controls/Hints/Hint_LegionGoDaemon.cs b/HandheldCompanion/Controls/Hints/Hint_LegionGoDaemon.cs
new file mode 100644
index 000000000..72342fb53
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/Hint_LegionGoDaemon.cs
@@ -0,0 +1,81 @@
+using HandheldCompanion.Devices;
+using HandheldCompanion.Views;
+using Microsoft.Win32.TaskScheduler;
+using System.Diagnostics;
+using System.Linq;
+using System.Timers;
+using System.Windows;
+using Task = System.Threading.Tasks.Task;
+
+namespace HandheldCompanion.Controls.Hints
+{
+ public class Hint_LegionGoDaemon : IHint
+ {
+ private Process process;
+ private const string taskName = "LSDaemon";
+ private Timer taskTimer;
+
+ public Hint_LegionGoDaemon() : base()
+ {
+ if (MainWindow.CurrentDevice is not LegionGo)
+ return;
+
+ taskTimer = new Timer(4000);
+ taskTimer.Elapsed += TaskTimer_Elapsed;
+ taskTimer.Start();
+
+ // default state
+ this.HintActionButton.Visibility = Visibility.Visible;
+
+ this.HintTitle.Text = Properties.Resources.Hint_LegionGoDaemon;
+ this.HintDescription.Text = Properties.Resources.Hint_LegionGoDaemonDesc;
+ this.HintReadMe.Text = Properties.Resources.Hint_LegionGoDaemonReadme;
+
+ this.HintActionButton.Content = Properties.Resources.Hint_LegionGoDaemonAction;
+ }
+
+ private void TaskTimer_Elapsed(object? sender, ElapsedEventArgs e)
+ {
+ // Get all the processes with the given name
+ process = Process.GetProcessesByName(taskName).FirstOrDefault();
+
+ // If there is at least one process, return true
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ if (process is not null && !process.HasExited)
+ this.Visibility = Visibility.Visible;
+ else
+ this.Visibility = Visibility.Collapsed;
+ });
+ }
+
+ protected override void HintActionButton_Click(object sender, RoutedEventArgs e)
+ {
+ Task.Run(async () =>
+ {
+ // Get the task service instance
+ using (TaskService ts = new TaskService())
+ {
+ // Get the task by name
+ Microsoft.Win32.TaskScheduler.Task task = ts.GetTask(taskName);
+ if (task != null && task.State == TaskState.Running)
+ {
+ task.Stop();
+ task.Enabled = false;
+ }
+ }
+ });
+
+ // If the process exists and is running, kill it
+ if (process != null && !process.HasExited)
+ process.Kill();
+ }
+
+ public override void Stop()
+ {
+ taskTimer.Stop();
+ base.Stop();
+ }
+ }
+}
diff --git a/HandheldCompanion/Controls/Hints/Hint_RogAllyServiceCheck.cs b/HandheldCompanion/Controls/Hints/Hint_RogAllyServiceCheck.cs
new file mode 100644
index 000000000..c0de6690d
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/Hint_RogAllyServiceCheck.cs
@@ -0,0 +1,109 @@
+using HandheldCompanion.Devices;
+using HandheldCompanion.Utils;
+using HandheldCompanion.Views;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.ServiceProcess;
+using System.Threading.Tasks;
+using System.Timers;
+using System.Windows;
+
+namespace HandheldCompanion.Controls.Hints
+{
+ public class Hint_RogAllyServiceCheck : IHint
+ {
+ private List serviceNames = new()
+ {
+ "ArmouryCrateSEService",
+ "AsusAppService",
+ "ArmouryCrateControlInterface",
+ };
+
+ private List serviceControllers = new();
+ private Timer serviceTimer;
+
+ public Hint_RogAllyServiceCheck() : base()
+ {
+ if (MainWindow.CurrentDevice is not ROGAlly)
+ return;
+
+ // Get all the services installed on the local computer
+ ServiceController[] services = ServiceController.GetServices();
+ foreach (string serviceName in serviceNames)
+ {
+ if (services.Any(s => serviceNames.Contains(s.ServiceName)))
+ {
+ // Create a service controller object for the specified service
+ ServiceController serviceController = new ServiceController(serviceName);
+ serviceControllers.Add(serviceController);
+ }
+ }
+
+ // Check if any of the services in the list exist
+ if (!serviceControllers.Any())
+ return;
+
+ serviceTimer = new Timer(4000);
+ serviceTimer.Elapsed += ServiceTimer_Elapsed;
+ serviceTimer.Start();
+
+ // default state
+ this.HintActionButton.Visibility = Visibility.Visible;
+
+ this.HintTitle.Text = Properties.Resources.Hint_RogAllyServiceCheck;
+ this.HintDescription.Text = Properties.Resources.Hint_RogAllyServiceCheckDesc;
+ this.HintReadMe.Text = Properties.Resources.Hint_RogAllyServiceCheckReadme;
+
+ this.HintActionButton.Content = Properties.Resources.Hint_RogAllyServiceCheckAction;
+ }
+
+ private void ServiceTimer_Elapsed(object? sender, ElapsedEventArgs e)
+ {
+ if(!serviceControllers.Any())
+ return;
+
+ // Check if any of the services in the list exist and are running
+ bool anyRunning = false;
+
+ foreach (ServiceController serviceController in serviceControllers)
+ {
+ serviceController.Refresh();
+ if (serviceController.Status == ServiceControllerStatus.Running)
+ {
+ anyRunning = true;
+ break;
+ }
+ }
+
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ this.Visibility = anyRunning ? Visibility.Visible : Visibility.Collapsed;
+ });
+ }
+
+ protected override void HintActionButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (!serviceControllers.Any())
+ return;
+
+ Task.Run(async () =>
+ {
+ foreach (ServiceController serviceController in serviceControllers)
+ {
+ if (serviceController.Status == ServiceControllerStatus.Running)
+ serviceController.Stop();
+ serviceController.WaitForStatus(ServiceControllerStatus.Stopped);
+ ServiceUtils.ChangeStartMode(serviceController, ServiceStartMode.Disabled, out _);
+ }
+ });
+ }
+
+ public override void Stop()
+ {
+ serviceTimer.Stop();
+ base.Stop();
+ }
+ }
+}
diff --git a/HandheldCompanion/Controls/Hints/Hint_SteamNeptuneDesktop.cs b/HandheldCompanion/Controls/Hints/Hint_SteamNeptuneDesktop.cs
new file mode 100644
index 000000000..2f3d1aea7
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/Hint_SteamNeptuneDesktop.cs
@@ -0,0 +1,70 @@
+using HandheldCompanion.Managers;
+using HandheldCompanion.Platforms;
+using System;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace HandheldCompanion.Controls.Hints
+{
+ public class Hint_SteamNeptuneDesktop : IHint
+ {
+ public Hint_SteamNeptuneDesktop() : base()
+ {
+ PlatformManager.Steam.Updated += Steam_Updated;
+ PlatformManager.Initialized += PlatformManager_Initialized;
+
+ // default state
+ this.HintActionButton.Visibility = Visibility.Visible;
+
+ this.HintTitle.Text = Properties.Resources.Hint_SteamNeptuneDesktop;
+ this.HintDescription.Text = Properties.Resources.Hint_SteamNeptuneDesktopDesc;
+ this.HintReadMe.Text = Properties.Resources.Hint_SteamNeptuneReadme;
+
+ this.HintActionButton.Content = Properties.Resources.Hint_SteamNeptuneAction;
+ }
+
+ private void Steam_Updated(PlatformStatus status)
+ {
+ bool DesktopProfileApplied = PlatformManager.Steam.HasDesktopProfileApplied();
+
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ switch (status)
+ {
+ default:
+ case PlatformStatus.Stopping:
+ case PlatformStatus.Stopped:
+ this.Visibility = Visibility.Collapsed;
+ break;
+ case PlatformStatus.Started:
+ this.Visibility = DesktopProfileApplied ? Visibility.Visible : Visibility.Collapsed;
+ break;
+ }
+ });
+ }
+
+ private void PlatformManager_Initialized()
+ {
+ Steam_Updated(PlatformManager.Steam.Status);
+ }
+
+ protected override void HintActionButton_Click(object sender, RoutedEventArgs e)
+ {
+ Task.Run(async () =>
+ {
+ PlatformManager.Steam.StopProcess();
+
+ while (PlatformManager.Steam.IsRunning)
+ await Task.Delay(1000);
+
+ PlatformManager.Steam.StartProcess();
+ });
+ }
+
+ public override void Stop()
+ {
+ base.Stop();
+ }
+ }
+}
diff --git a/HandheldCompanion/Controls/Hints/Hint_SteamXboxDrivers.cs b/HandheldCompanion/Controls/Hints/Hint_SteamXboxDrivers.cs
new file mode 100644
index 000000000..7772282ea
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/Hint_SteamXboxDrivers.cs
@@ -0,0 +1,48 @@
+using HandheldCompanion.Managers;
+using HandheldCompanion.Platforms;
+using System.Windows;
+
+namespace HandheldCompanion.Controls.Hints
+{
+ public class Hint_SteamXboxDrivers : IHint
+ {
+ public Hint_SteamXboxDrivers() : base()
+ {
+ PlatformManager.Steam.Updated += Steam_Updated;
+ PlatformManager.Initialized += PlatformManager_Initialized;
+
+ // default state
+ this.HintActionButton.Visibility = Visibility.Collapsed;
+
+ this.HintTitle.Text = Properties.Resources.Hint_SteamXboxDrivers;
+ this.HintDescription.Text = Properties.Resources.Hint_SteamXboxDriversDesc;
+ this.HintReadMe.Text = Properties.Resources.Hint_SteamXboxDriversReadme;
+ }
+
+ private void Steam_Updated(PlatformStatus status)
+ {
+ CheckDrivers();
+ }
+
+ private void PlatformManager_Initialized()
+ {
+ CheckDrivers();
+ }
+
+ private void CheckDrivers()
+ {
+ bool HasXboxDriversInstalled = PlatformManager.Steam.HasXboxDriversInstalled();
+
+ // UI thread (async)
+ Application.Current.Dispatcher.BeginInvoke(() =>
+ {
+ this.Visibility = HasXboxDriversInstalled ? Visibility.Visible : Visibility.Collapsed;
+ });
+ }
+
+ public override void Stop()
+ {
+ base.Stop();
+ }
+ }
+}
diff --git a/HandheldCompanion/Controls/Hints/IHint.xaml b/HandheldCompanion/Controls/Hints/IHint.xaml
new file mode 100644
index 000000000..b93cf86f0
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/IHint.xaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HandheldCompanion/Controls/Hints/IHint.xaml.cs b/HandheldCompanion/Controls/Hints/IHint.xaml.cs
new file mode 100644
index 000000000..b92cbf8a5
--- /dev/null
+++ b/HandheldCompanion/Controls/Hints/IHint.xaml.cs
@@ -0,0 +1,22 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace HandheldCompanion.Controls.Hints
+{
+ ///
+ /// Interaction logic for IHint.xaml
+ ///
+ public partial class IHint : UserControl
+ {
+ public IHint()
+ {
+ InitializeComponent();
+ }
+
+ protected virtual void HintActionButton_Click(object sender, RoutedEventArgs e)
+ { }
+
+ public virtual void Stop()
+ { }
+ }
+}
diff --git a/HandheldCompanion/Controls/Mapping/AxisMapping.xaml b/HandheldCompanion/Controls/Mapping/AxisMapping.xaml
index 236ea07ef..1bac32689 100644
--- a/HandheldCompanion/Controls/Mapping/AxisMapping.xaml
+++ b/HandheldCompanion/Controls/Mapping/AxisMapping.xaml
@@ -150,6 +150,7 @@
Style="{DynamicResource SliderStyle1}"
TickFrequency="90"
TickPlacement="BottomRight"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_Rotation_Slider_ValueChanged" />
@@ -186,6 +187,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_InnerDeadzone_Slider_ValueChanged" />
@@ -222,6 +224,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_OuterDeadzone_Slider_ValueChanged" />
@@ -257,6 +260,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_AntiDeadZone_Slider_ValueChanged" />
@@ -333,6 +337,7 @@
Style="{DynamicResource SliderStyle1}"
TickFrequency="90"
TickPlacement="BottomRight"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MouseRotation_ValueChanged" />
@@ -368,6 +373,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MousePointerSpeed_ValueChanged"
Value="33" />
@@ -405,6 +411,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="0.05"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MouseAcceleration_ValueChanged"
Value="1.00" />
@@ -468,6 +475,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="0.005"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MouseFilterCutoff_ValueChanged"
Value="0.05" />
@@ -505,6 +513,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MouseDeadzone_ValueChanged"
Value="10" />
diff --git a/HandheldCompanion/Controls/Mapping/ButtonMapping.xaml b/HandheldCompanion/Controls/Mapping/ButtonMapping.xaml
index d5277e8bc..1f4394781 100644
--- a/HandheldCompanion/Controls/Mapping/ButtonMapping.xaml
+++ b/HandheldCompanion/Controls/Mapping/ButtonMapping.xaml
@@ -104,6 +104,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="50"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="LongPressDelaySlider_ValueChanged"
Value="450" />
@@ -228,6 +229,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="5"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Turbo_Slider_ValueChanged"
Value="30" />
diff --git a/HandheldCompanion/Controls/Mapping/GyroMapping.xaml b/HandheldCompanion/Controls/Mapping/GyroMapping.xaml
index 7c552dc3b..206ee2186 100644
--- a/HandheldCompanion/Controls/Mapping/GyroMapping.xaml
+++ b/HandheldCompanion/Controls/Mapping/GyroMapping.xaml
@@ -203,6 +203,7 @@
Style="{DynamicResource SliderStyle1}"
TickFrequency="0.1"
TickPlacement="BottomRight"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Slider_GyroWeight_ValueChanged" />
@@ -292,6 +293,7 @@
Style="{DynamicResource SliderStyle1}"
TickFrequency="90"
TickPlacement="BottomRight"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_Rotation_Slider_ValueChanged" />
@@ -328,6 +330,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_InnerDeadzone_Slider_ValueChanged" />
@@ -364,6 +367,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_OuterDeadzone_Slider_ValueChanged" />
@@ -399,6 +403,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis_AntiDeadZone_Slider_ValueChanged" />
@@ -475,6 +480,7 @@
Style="{DynamicResource SliderStyle1}"
TickFrequency="90"
TickPlacement="BottomRight"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MouseRotation_ValueChanged" />
@@ -510,6 +516,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MousePointerSpeed_ValueChanged" />
@@ -546,6 +553,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="0.05"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MouseAcceleration_ValueChanged"
Value="1.00" />
@@ -582,6 +590,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Axis2MouseDeadzone_ValueChanged" />
diff --git a/HandheldCompanion/Controls/Mapping/GyroMapping.xaml.cs b/HandheldCompanion/Controls/Mapping/GyroMapping.xaml.cs
index b4a19256c..5dae39bec 100644
--- a/HandheldCompanion/Controls/Mapping/GyroMapping.xaml.cs
+++ b/HandheldCompanion/Controls/Mapping/GyroMapping.xaml.cs
@@ -433,7 +433,7 @@ private void cB_Input_SelectionChanged(object sender, SelectionChangedEventArgs
return;
MotionInput input = (MotionInput)cB_Input.SelectedIndex;
- Text_InputHint.Text = Profile.InputDescription[input];
+ Text_InputHint.Text = EnumUtils.GetDescriptionFromEnumValue(input, string.Empty, "Desc");
((GyroActions)this.Actions).MotionInput = (MotionInput)cB_Input.SelectedIndex;
diff --git a/HandheldCompanion/Controls/Mapping/TriggerMapping.xaml b/HandheldCompanion/Controls/Mapping/TriggerMapping.xaml
index 4820bee54..0d1c04207 100644
--- a/HandheldCompanion/Controls/Mapping/TriggerMapping.xaml
+++ b/HandheldCompanion/Controls/Mapping/TriggerMapping.xaml
@@ -98,6 +98,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Trigger2TriggerInnerDeadzone_ValueChanged" />
@@ -134,6 +135,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Trigger2TriggerOuterDeadzone_ValueChanged" />
@@ -169,6 +171,7 @@
ScrollViewer.PanningMode="HorizontalOnly"
Style="{DynamicResource SliderStyle1}"
TickFrequency="1"
+ ToolTip="{Binding Value, StringFormat=N0, RelativeSource={RelativeSource Self}, Mode=OneWay}"
ValueChanged="Trigger2TriggerAntiDeadzone_ValueChanged" />
diff --git a/HandheldCompanion/Controls/ProcessEx.xaml b/HandheldCompanion/Controls/ProcessEx.xaml
index 55c369776..1d2382797 100644
--- a/HandheldCompanion/Controls/ProcessEx.xaml
+++ b/HandheldCompanion/Controls/ProcessEx.xaml
@@ -58,16 +58,16 @@
Toggled="SuspendToggle_Toggled" />
-
+
-
+
+ Click="B_KillProcess_Clicked"
+ Content="Kill process"
+ Style="{DynamicResource AccentButtonStyle}" />
diff --git a/HandheldCompanion/Controls/ProcessEx.xaml.cs b/HandheldCompanion/Controls/ProcessEx.xaml.cs
index 553dcd906..2f5475195 100644
--- a/HandheldCompanion/Controls/ProcessEx.xaml.cs
+++ b/HandheldCompanion/Controls/ProcessEx.xaml.cs
@@ -210,6 +210,7 @@ private void SuspendToggle_Toggled(object sender, RoutedEventArgs e)
private void B_KillProcess_Clicked(object sender, RoutedEventArgs e)
{
- Process.Kill();
+ if (Process is not null)
+ Process.Kill();
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/DSU/DSUServer.cs b/HandheldCompanion/DSU/DSUServer.cs
index d5c598a2f..18355ab96 100644
--- a/HandheldCompanion/DSU/DSUServer.cs
+++ b/HandheldCompanion/DSU/DSUServer.cs
@@ -11,6 +11,7 @@
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
+using Timer = System.Timers.Timer;
namespace HandheldCompanion;
@@ -70,11 +71,9 @@ public class DSUServer
public const int NUMBER_SLOTS = 4;
private const int ARG_BUFFER_LEN = 80;
- protected const short UPDATE_INTERVAL = 10;
-
private const ushort MaxProtocolVersion = 1001;
- private readonly SemaphoreSlim _pool;
- private readonly SocketAsyncEventArgs[] argsList;
+ private SemaphoreSlim _pool;
+ private SocketAsyncEventArgs[] argsList;
private readonly Dictionary clients = new();
@@ -94,6 +93,7 @@ public class DSUServer
private uint serverId;
private int udpPacketCount;
private Socket udpSock;
+ private Timer udpTimer;
public DSUServer()
{
@@ -111,8 +111,24 @@ public DSUServer()
PadState = DsState.Connected
};
+ udpTimer = new(5000);
+ udpTimer.AutoReset = false;
+ udpTimer.Elapsed += UdpTimer_Elapsed;
+ }
+
+ private void ResetPool()
+ {
+ if (_pool is not null)
+ _pool.Release();
+
_pool = new SemaphoreSlim(ARG_BUFFER_LEN);
+
+ if (argsList is not null && argsList.Length != 0)
+ foreach(SocketAsyncEventArgs args in argsList)
+ args.Dispose();
+
argsList = new SocketAsyncEventArgs[ARG_BUFFER_LEN];
+
for (int num = 0; num < ARG_BUFFER_LEN; num++)
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
@@ -232,13 +248,10 @@ private void ProcessIncoming(byte[] localMsg, IPEndPoint clientEP)
packetSize += 16; //size of header
if (packetSize > localMsg.Length)
- {
return;
- }
-
- if (packetSize < localMsg.Length)
+ else if (packetSize < localMsg.Length)
{
- var newMsg = new byte[packetSize];
+ byte[] newMsg = new byte[packetSize];
Array.Copy(localMsg, newMsg, packetSize);
localMsg = newMsg;
}
@@ -366,29 +379,28 @@ private void ReceiveCallback(IAsyncResult iar)
try
{
- if (running)
- {
- //Get the received message.
- var recvSock = (Socket)iar.AsyncState;
+ //Get the received message.
+ Socket recvSock = (Socket)iar.AsyncState;
+ int msgLen = recvSock.EndReceiveFrom(iar, ref clientEP);
- var msgLen = recvSock.EndReceiveFrom(iar, ref clientEP);
- localMsg = new byte[msgLen];
- Array.Copy(recvBuffer, localMsg, msgLen);
- }
+ localMsg = new byte[msgLen];
+ Array.Copy(recvBuffer, localMsg, msgLen);
}
- catch
+ catch (SocketException)
{
- var IOC_IN = 0x80000000;
- uint IOC_VENDOR = 0x18000000;
- var SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
- udpSock?.IOControl((int)SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null);
+ if (running)
+ {
+ ResetUDPConn();
+ return;
+ }
}
+ catch (Exception /*e*/) { }
//Start another receive as soon as we copied the data
StartReceive();
//Process the data if its valid
- if (localMsg is not null)
+ if (localMsg != null)
ProcessIncoming(localMsg, (IPEndPoint)clientEP);
}
@@ -400,29 +412,48 @@ private void StartReceive()
{
//Start listening for a new message.
EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0);
- udpSock?.BeginReceiveFrom(recvBuffer, 0, recvBuffer.Length, SocketFlags.None, ref newClientEP,
- ReceiveCallback, udpSock);
+ udpSock.BeginReceiveFrom(recvBuffer, 0, recvBuffer.Length, SocketFlags.None, ref newClientEP, ReceiveCallback, udpSock);
}
}
- catch
+ catch (SocketException /*ex*/)
{
- var IOC_IN = 0x80000000;
- uint IOC_VENDOR = 0x18000000;
- var SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
- udpSock?.IOControl((int)SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null);
-
- StartReceive();
+ if (running)
+ {
+ ResetUDPConn();
+ return;
+ }
}
+ catch (Exception /*ex*/) { }
}
- public bool Start()
+ ///
+ /// Used to send CONNRESET ioControlCode to Socket used for UDP Server.
+ /// Frees Socket from potentially firing SocketException instances after a client
+ /// connection is terminated. Avoids memory leak
+ ///
+ private void ResetUDPConn()
{
+ // suspend UDP
if (running)
Stop();
- udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ udpTimer.Stop();
+ udpTimer.Start();
+ }
+
+ private void UdpTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
+ {
+ // resume UDP
+ Start();
+ }
+
+ public bool Start()
+ {
+ ResetPool();
+
try
{
+ udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
udpSock.Bind(new IPEndPoint(IPAddress.Any, port));
}
catch (SocketException)
@@ -431,6 +462,7 @@ public bool Start()
Stop();
return running;
}
+ catch (Exception /*ex*/) { }
var randomBuf = new byte[4];
new Random().NextBytes(randomBuf);
@@ -452,7 +484,11 @@ public void Stop()
{
if (udpSock is not null)
{
+ if (udpSock.Connected)
+ udpSock.Disconnect(true);
+
udpSock.Close();
+ udpSock.Dispose();
udpSock = null;
}
@@ -699,30 +735,24 @@ public void Tick(long ticks)
foreach (var cl in clientsList)
{
//try { udpSock.SendTo(outputData, cl); }
- var temp = 0;
+ int temp = 0;
poolLock.EnterWriteLock();
temp = listInd;
listInd = ++listInd % ARG_BUFFER_LEN;
- var args = argsList[temp];
+ SocketAsyncEventArgs args = argsList[temp];
poolLock.ExitWriteLock();
_pool.Wait();
args.RemoteEndPoint = cl;
Array.Copy(outputData, args.Buffer, outputData.Length);
- var sentAsync = false;
+ bool sentAsync = false;
try
{
- sentAsync = udpSock.SendToAsync(args);
- }
- catch (SocketException)
- {
- }
- catch (ObjectDisposedException)
- {
- }
- catch (NullReferenceException)
- {
+ bool sendAsync = udpSock.SendToAsync(args);
+ if (!sendAsync) CompletedSynchronousSocketEvent();
}
+ catch (SocketException /*ex*/) { }
+ catch (Exception /*ex*/) { }
finally
{
if (!sentAsync) CompletedSynchronousSocketEvent();
@@ -731,6 +761,7 @@ public void Tick(long ticks)
}
clientsList.Clear();
+ clientsList = null;
}
private enum MessageType
@@ -743,38 +774,42 @@ private enum MessageType
DSUS_PadDataRsp = 0x100002
}
- private class ClientRequestTimes
+ class ClientRequestTimes
{
+ DateTime allPads;
+ DateTime[] padIds;
+ Dictionary padMacs;
+
+ public DateTime AllPadsTime { get { return allPads; } }
+ public DateTime[] PadIdsTime { get { return padIds; } }
+ public Dictionary PadMacsTime { get { return padMacs; } }
+
public ClientRequestTimes()
{
- AllPadsTime = DateTime.MinValue;
- PadIdsTime = new DateTime[4];
+ allPads = DateTime.MinValue;
+ padIds = new DateTime[4];
- for (var i = 0; i < PadIdsTime.Length; i++)
- PadIdsTime[i] = DateTime.MinValue;
+ for (int i = 0; i < padIds.Length; i++)
+ padIds[i] = DateTime.MinValue;
- PadMacsTime = new Dictionary();
+ padMacs = new Dictionary();
}
- public DateTime AllPadsTime { get; private set; }
-
- public DateTime[] PadIdsTime { get; }
-
- public Dictionary PadMacsTime { get; }
-
public void RequestPadInfo(byte regFlags, byte idToReg, PhysicalAddress macToReg)
{
if (regFlags == 0)
- {
- AllPadsTime = DateTime.UtcNow;
- }
+ allPads = DateTime.UtcNow;
else
{
if ((regFlags & 0x01) != 0) //id valid
- if (idToReg < PadIdsTime.Length)
- PadIdsTime[idToReg] = DateTime.UtcNow;
+ {
+ if (idToReg < padIds.Length)
+ padIds[idToReg] = DateTime.UtcNow;
+ }
if ((regFlags & 0x02) != 0) //mac valid
- PadMacsTime[macToReg] = DateTime.UtcNow;
+ {
+ padMacs[macToReg] = DateTime.UtcNow;
+ }
}
}
}
diff --git a/HandheldCompanion/Devices/AOKZOE/AOKZOEA1.cs b/HandheldCompanion/Devices/AOKZOE/AOKZOEA1.cs
index 9d69f134a..c7a70a88e 100644
--- a/HandheldCompanion/Devices/AOKZOE/AOKZOEA1.cs
+++ b/HandheldCompanion/Devices/AOKZOE/AOKZOEA1.cs
@@ -18,16 +18,17 @@ public AOKZOEA1()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 4, 28 };
GfxClock = new double[] { 100, 2200 };
+ CpuClock = 4700;
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxis = new Vector3(-1.0f, 1.0f, -1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(-1.0f, 1.0f, -1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
@@ -39,12 +40,12 @@ public AOKZOEA1()
ECDetails = new ECDetails
{
- AddressControl = 0x44A,
- AddressDuty = 0x44B,
- AddressRegistry = 0x4E,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 184
+ AddressFanControl = 0x44A,
+ AddressFanDuty = 0x44B,
+ AddressStatusCommandPort = 0x4E, // 78
+ AddressDataPort = 0x4F, // 79
+ FanValueMin = 0,
+ FanValueMax = 184
};
// Home
@@ -112,4 +113,19 @@ public override void Close()
ECRamDirectWrite(0x4F2, ECDetails, 0x00);
base.Close();
}
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\u220C";
+ case ButtonFlags.OEM2:
+ return "\u2210";
+ case ButtonFlags.OEM3:
+ return "\u2211";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AOKZOE/AOKZOEA1Pro.cs b/HandheldCompanion/Devices/AOKZOE/AOKZOEA1Pro.cs
index fc03b1363..8ef12d4ae 100644
--- a/HandheldCompanion/Devices/AOKZOE/AOKZOEA1Pro.cs
+++ b/HandheldCompanion/Devices/AOKZOE/AOKZOEA1Pro.cs
@@ -12,5 +12,6 @@ public AOKZOEA1Pro()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 4, 28 };
GfxClock = new double[] { 100, 2700 };
+ CpuClock = 5100;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/ASUS/AsusACPI.cs b/HandheldCompanion/Devices/ASUS/AsusACPI.cs
index caf644f03..801fb70e5 100644
--- a/HandheldCompanion/Devices/ASUS/AsusACPI.cs
+++ b/HandheldCompanion/Devices/ASUS/AsusACPI.cs
@@ -1,8 +1,8 @@
-using System.Management;
-using System.Runtime.InteropServices;
+using HandheldCompanion.Managers;
using System;
+using System.Management;
+using System.Runtime.InteropServices;
using System.Threading;
-using HandheldCompanion.Managers;
public enum AsusFan
{
@@ -14,7 +14,7 @@ public enum AsusFan
public enum AsusMode
{
- Balanced = 0,
+ Performance = 0,
Turbo = 1,
Silent = 2
}
diff --git a/HandheldCompanion/Devices/ASUS/ROGAlly.cs b/HandheldCompanion/Devices/ASUS/ROGAlly.cs
index 833da66c4..ceeaa5208 100644
--- a/HandheldCompanion/Devices/ASUS/ROGAlly.cs
+++ b/HandheldCompanion/Devices/ASUS/ROGAlly.cs
@@ -1,14 +1,18 @@
using HandheldCompanion.Devices.ASUS;
using HandheldCompanion.Inputs;
+using HandheldCompanion.Managers;
using HandheldCompanion.Utils;
using HidLibrary;
using Nefarius.Utilities.DeviceManagement.PnP;
using System;
using System.Collections.Generic;
using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
using WindowsInput.Events;
+using static HandheldCompanion.Utils.DeviceUtils;
using Task = System.Threading.Tasks.Task;
-using System.Threading.Tasks;
namespace HandheldCompanion.Devices;
@@ -28,9 +32,51 @@ public class ROGAlly : IDevice
private AsusACPI asusACPI;
private const byte INPUT_HID_ID = 0x5a;
+ private const byte AURA_HID_ID = 0x5d;
+ private const int ASUS_ID = 0x0b05;
+
+ public static readonly byte[] LED_INIT1 = new byte[] { AURA_HID_ID, 0xb9 };
+ public static readonly byte[] LED_INIT2 = Encoding.ASCII.GetBytes("]ASUS Tech.Inc.");
+ public static readonly byte[] LED_INIT3 = new byte[] { AURA_HID_ID, 0x05, 0x20, 0x31, 0, 0x1a };
+ public static readonly byte[] LED_INIT4 = Encoding.ASCII.GetBytes("^ASUS Tech.Inc.");
+ public static readonly byte[] LED_INIT5 = new byte[] { 0x5e, 0x05, 0x20, 0x31, 0, 0x1a };
+
+ static byte[] MESSAGE_APPLY = { AURA_HID_ID, 0xb4 };
+ static byte[] MESSAGE_SET = { AURA_HID_ID, 0xb5, 0, 0, 0 };
public override bool IsOpen => hidDevices.ContainsKey(INPUT_HID_ID) && hidDevices[INPUT_HID_ID].IsOpen && asusACPI is not null && asusACPI.IsOpen();
+ private enum AuraMode
+ {
+ SolidColor = 0,
+ Breathing = 1,
+ Wheel = 2,
+ Rainbow = 3,
+ Wave = 4,
+ }
+
+ private enum AuraSpeed
+ {
+ Slow = 0xeb,
+ Medium = 0xf5,
+ Fast = 0xe1,
+ }
+
+ private enum AuraDirection
+ {
+ Forward = 0,
+ Reverse = 1,
+ }
+
+ private enum LEDZone
+ {
+ All = 0,
+ JoystickLeftSideLeft = 1,
+ JoystickLeftSideRight = 2,
+ JoystickRightSideLeft = 3,
+ JoystickRightSideRight = 4,
+ }
+
public ROGAlly()
{
// device specific settings
@@ -46,17 +92,18 @@ public ROGAlly()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 5, 30 };
GfxClock = new double[] { 100, 2700 };
+ CpuClock = 5100;
- AngularVelocityAxis = new Vector3(-1.0f, 1.0f, 1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxis = new Vector3(-1.0f, 1.0f, 1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(1.0f, 1.0f, 1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
@@ -64,7 +111,41 @@ public ROGAlly()
};
// device specific capacities
- Capabilities = DeviceCapabilities.FanControl;
+ Capabilities |= DeviceCapabilities.FanControl;
+ Capabilities |= DeviceCapabilities.DynamicLighting;
+ Capabilities |= DeviceCapabilities.DynamicLightingBrightness;
+
+ // dynamic lighting capacities
+ DynamicLightingCapabilities |= LEDLevel.SolidColor;
+ DynamicLightingCapabilities |= LEDLevel.Breathing;
+ DynamicLightingCapabilities |= LEDLevel.Rainbow;
+ // DynamicLightingCapabilities |= LEDLevel.Wave;
+ DynamicLightingCapabilities |= LEDLevel.Wheel;
+ DynamicLightingCapabilities |= LEDLevel.Ambilight;
+
+ powerProfileQuiet = new(Properties.Resources.PowerProfileSilentName, Properties.Resources.PowerProfileSilentDescription)
+ {
+ Default = true,
+ OEMPowerMode = (int)AsusMode.Silent,
+ OSPowerMode = PowerMode.BetterBattery,
+ Guid = PowerMode.BetterBattery
+ };
+
+ powerProfileBalanced = new(Properties.Resources.PowerProfilePerformanceName, Properties.Resources.PowerProfilePerformanceDescription)
+ {
+ Default = true,
+ OEMPowerMode = (int)AsusMode.Performance,
+ OSPowerMode = PowerMode.BetterPerformance,
+ Guid = PowerMode.BetterPerformance
+ };
+
+ powerProfileCool = new(Properties.Resources.PowerProfileTurboName, Properties.Resources.PowerProfileTurboDescription)
+ {
+ Default = true,
+ OEMPowerMode = (int)AsusMode.Turbo,
+ OSPowerMode = PowerMode.BestPerformance,
+ Guid = PowerMode.BestPerformance
+ };
OEMChords.Add(new DeviceChord("CC",
new List(), new List(),
@@ -76,12 +157,157 @@ public ROGAlly()
false, ButtonFlags.OEM2
));
- OEMChords.Add(new DeviceChord("M1/M2",
- new List(), new List(),
+ // M1 and M2 do a repeating input when holding the button
+ OEMChords.Add(new DeviceChord("M1",
+ new List { KeyCode.F17 },
+ new List { KeyCode.F17 },
false, ButtonFlags.OEM3
));
+
+ OEMChords.Add(new DeviceChord("M2",
+ new List { KeyCode.F18 },
+ new List { KeyCode.F18 },
+ false, ButtonFlags.OEM4
+ ));
}
+ private byte[] flushBufferWriteChanges = new byte[64]
+ {
+ 0x5A, 0xD1, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] modeGame = new byte[64]
+ {
+ 0x5A, 0xD1, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] modeMouse = new byte[64]
+ {
+ 0x5A, 0xD1, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] dPadUpDownDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x01, 0x2C, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8C, 0x88, 0x76, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] dPadLeftRightDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x02, 0x2C, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00, 0x01, 0x0C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0D, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] joySticksDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x03, 0x2C, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] shoulderButtonsDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x04, 0x2C, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] faceButtonsABDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x05, 0x2C, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] faceButtonsXYDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x06, 0x2C, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4D, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] viewAndMenuDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x07, 0x2C, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] M1M2Default = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x08, 0x2C, 0x02, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x8F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] M1F17M2F18 = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x08, 0x2C, 0x02, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x30, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] triggersDefault = new byte[64]
+ {
+ 0x5A, 0xD1, 0x02, 0x09, 0x2C, 0x01, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] commitReset1of4 = new byte[64]
+ {
+ 0x5A, 0xD1, 0x0F, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+ };
+
+ private byte[] commitReset2of4 = new byte[64]
+ {
+ 0x5A, 0xD1, 0x06, 0x02, 0x64, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] commitReset3of4 = new byte[64]
+ {
+ 0x5A, 0xD1, 0x04, 0x04, 0x00, 0x64, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private byte[] commitReset4of4 = new byte[64]
+ {
+ 0x5A, 0xD1, 0x05, 0x04, 0x00, 0x64, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
public override bool Open()
{
var success = base.Open();
@@ -93,6 +319,9 @@ public override bool Open()
if (asusACPI is null)
return false;
+ // force M1/M2 to send F17 and F18
+ ConfigureController(true);
+
return true;
}
@@ -102,9 +331,21 @@ public override void Close()
if (asusACPI is not null)
asusACPI.Close();
+ // restore default M1/M2 behavior
+ ConfigureController(false);
+
// close devices
foreach (HidDevice hidDevice in hidDevices.Values)
- hidDevice.CloseDevice();
+ {
+ if (!hidDevice.IsConnected)
+ continue;
+
+ if (hidDevice.IsOpen)
+ {
+ hidDevice.MonitorDeviceEvents = false;
+ hidDevice.CloseDevice();
+ }
+ }
base.Close();
}
@@ -127,6 +368,10 @@ public override bool IsReady()
Task ReportDevice = Task.Run(async () => await device.ReadReportAsync());
ReportDevice.ContinueWith(t => OnReport(ReportDevice.Result, device));
}
+ else if (device.ReadFeatureData(out data, AURA_HID_ID))
+ {
+ hidDevices[AURA_HID_ID] = device;
+ }
}
hidDevices.TryGetValue(INPUT_HID_ID, out HidDevice hidDevice);
@@ -153,7 +398,7 @@ private void OnReport(HidReport result, HidDevice device)
HandleEvent(key);
}
- public override void SetFanControl(bool enable)
+ public override void SetFanControl(bool enable, int mode = 0)
{
if (!IsOpen)
return;
@@ -161,7 +406,7 @@ public override void SetFanControl(bool enable)
switch (enable)
{
case false:
- asusACPI.DeviceSet(AsusACPI.PerformanceMode, (int)AsusMode.Turbo);
+ asusACPI.DeviceSet(AsusACPI.PerformanceMode, mode);
return;
}
}
@@ -207,13 +452,6 @@ private void HandleEvent(byte key)
ButtonFlags button = keyMapping[key];
switch (key)
{
- case 0: // Back paddles: Release
- {
- KeyRelease(ButtonFlags.OEM3);
- }
- return;
-
- case 165: // Back paddles: Press
case 167: // Armory crate: Hold
KeyPress(button);
break;
@@ -236,4 +474,213 @@ private void HandleEvent(byte key)
break;
}
}
+
+ public override bool SetLedBrightness(int brightness)
+ {
+ //ROG ALly brightness range is: 0 - 3 range, 0 is off, convert from 0 - 100 % range
+ brightness = (int)Math.Round(brightness / 33.33);
+
+ if (hidDevices.TryGetValue(AURA_HID_ID, out HidDevice hidDevice))
+ {
+ if (!hidDevice.IsConnected)
+ return false;
+
+ byte[] msg = { AURA_HID_ID, 0xba, 0xc5, 0xc4, (byte)brightness };
+ return hidDevice.WriteFeatureData(msg);
+ }
+
+ return false;
+ }
+
+ public override bool SetLedColor(Color MainColor, Color SecondaryColor, LEDLevel level, int speed)
+ {
+ if (!DynamicLightingCapabilities.HasFlag(level))
+ return false;
+
+ // Apply the color for the left and right LED
+ AuraMode auraMode = AuraMode.SolidColor;
+
+ switch (level)
+ {
+ case LEDLevel.SolidColor:
+ auraMode = AuraMode.SolidColor;
+ break;
+ case LEDLevel.Breathing:
+ auraMode = AuraMode.Breathing;
+ break;
+ case LEDLevel.Rainbow:
+ auraMode = AuraMode.Rainbow;
+ break;
+ case LEDLevel.Wave:
+ auraMode = AuraMode.Wave;
+ break;
+ case LEDLevel.Wheel:
+ auraMode = AuraMode.Wheel;
+ break;
+ case LEDLevel.Ambilight:
+ return ApplyColorFast(MainColor, SecondaryColor);
+ }
+
+ AuraSpeed auraSpeed = AuraSpeed.Fast;
+ if (speed <= 33)
+ auraSpeed = AuraSpeed.Slow;
+ else if (speed > 33 && speed <= 66)
+ auraSpeed = AuraSpeed.Medium;
+ else
+ auraSpeed = AuraSpeed.Fast;
+
+ return ApplyColor(auraMode, MainColor, SecondaryColor, auraSpeed);
+ }
+
+ private bool ApplyColor(AuraMode mode, Color MainColor, Color SecondaryColor, AuraSpeed speed = AuraSpeed.Slow, AuraDirection direction = AuraDirection.Forward)
+ {
+ if (hidDevices.TryGetValue(AURA_HID_ID, out HidDevice hidDevice))
+ {
+ if (!hidDevice.IsConnected)
+ return false;
+
+ hidDevice.Write(AuraMessage(mode, MainColor, SecondaryColor, speed, LEDZone.All));
+ hidDevice.Write(MESSAGE_APPLY);
+ hidDevice.Write(MESSAGE_SET);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool ApplyColorFast(Color MainColor, Color SecondaryColor)
+ {
+ if (hidDevices.TryGetValue(AURA_HID_ID, out HidDevice hidDevice))
+ {
+ if (!hidDevice.IsConnected)
+ return false;
+
+ // Left joystick
+ hidDevice.Write(AuraMessage(AuraMode.SolidColor, MainColor, MainColor, AuraSpeed.Slow, LEDZone.JoystickLeftSideLeft));
+ hidDevice.Write(AuraMessage(AuraMode.SolidColor, MainColor, MainColor, AuraSpeed.Slow, LEDZone.JoystickLeftSideRight));
+
+ // Right joystick
+ hidDevice.Write(AuraMessage(AuraMode.SolidColor, SecondaryColor, SecondaryColor, AuraSpeed.Slow, LEDZone.JoystickRightSideLeft));
+ hidDevice.Write(AuraMessage(AuraMode.SolidColor, SecondaryColor, SecondaryColor, AuraSpeed.Slow, LEDZone.JoystickRightSideRight));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static byte[] AuraMessage(AuraMode mode, Color LEDColor1, Color LEDColor2, AuraSpeed speed, LEDZone zone, LEDDirection direction = LEDDirection.Up)
+ {
+ byte[] msg = new byte[17];
+ msg[0] = AURA_HID_ID;
+ msg[1] = 0xb3;
+ msg[2] = (byte)zone; // Zone
+ msg[3] = (byte)mode; // Aura Mode
+ msg[4] = LEDColor1.R; // R
+ msg[5] = LEDColor1.G; // G
+ msg[6] = LEDColor1.B; // B
+ msg[7] = (byte)speed; // aura.speed as u8;
+ msg[8] = (byte)direction; // aura.direction as u8;
+ msg[9] = (mode == AuraMode.Breathing) ? (byte)1 : (byte)0;
+ msg[10] = LEDColor2.R; // R
+ msg[11] = LEDColor2.G; // G
+ msg[12] = LEDColor2.B; // B
+ return msg;
+ }
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\uE006";
+ case ButtonFlags.OEM2:
+ return "\uE005";
+ case ButtonFlags.OEM3:
+ return "\u2212";
+ case ButtonFlags.OEM4:
+ return "\u2213";
+ }
+
+ return defaultGlyph;
+ }
+
+ private void ConfigureController(bool Remap)
+ {
+ /*
+ Generic function
+ 23 HID commands of 64 bytes each
+
+ 1. Mode
+ 2. Flush buffer, write changes
+ 3. DPad up and down
+ 4. Flush buffer, write changes
+ 5. DPad left and right
+ 6. Flush buffer, write changes
+ 7. JoysSticks
+ 8. Flush buffer, write changes
+ 9. Shoulder buttons
+ 10. Flush buffer, write changes
+ 11. AB Facebuttons
+ 12. Flush buffer, write changes
+ 13. XY Facebuttons
+ 14. Flush buffer, write changes
+ 15. View and menu
+ 16. Flush buffer, write changes
+ 17. M1 and M2
+ 18. Flush buffer, write changes
+ 19. Triggers
+ 20. Commit and reset 1 of 4
+ 21. Commit and reset 2 of 4
+ 22. Commit and reset 3 of 4
+ 23. Commit and reset 4 of 4
+ */
+
+ SendHidControlWrite(modeGame); // 1
+ SendHidControlWrite(flushBufferWriteChanges); // 2
+
+ SendHidControlWrite(dPadUpDownDefault); // 3
+ SendHidControlWrite(flushBufferWriteChanges); // 4
+
+ SendHidControlWrite(dPadLeftRightDefault); // 5
+ SendHidControlWrite(flushBufferWriteChanges); // 6
+
+ SendHidControlWrite(joySticksDefault); // 7
+ SendHidControlWrite(flushBufferWriteChanges); // 8
+
+ SendHidControlWrite(shoulderButtonsDefault); // 9
+ SendHidControlWrite(flushBufferWriteChanges); // 10
+
+ SendHidControlWrite(faceButtonsABDefault); // 11
+ SendHidControlWrite(flushBufferWriteChanges); // 12
+
+ SendHidControlWrite(faceButtonsXYDefault); // 13
+ SendHidControlWrite(flushBufferWriteChanges); // 14
+
+ SendHidControlWrite(viewAndMenuDefault); // 15
+ SendHidControlWrite(flushBufferWriteChanges); // 16
+
+ // Choose the appropriate mapping based on the 'Remap' flag
+ SendHidControlWrite(Remap ? M1F17M2F18 : M1M2Default); // Step 17
+
+ SendHidControlWrite(flushBufferWriteChanges); // 18
+
+ SendHidControlWrite(triggersDefault); // 19
+
+ SendHidControlWrite(commitReset1of4); // 20
+ SendHidControlWrite(commitReset2of4); // 21
+ SendHidControlWrite(commitReset3of4); // 22
+ SendHidControlWrite(commitReset4of4); // 23
+ }
+
+ public void SendHidControlWrite(byte[] data)
+ {
+ if (hidDevices.TryGetValue(INPUT_HID_ID, out HidDevice device))
+ {
+ if (device.IsConnected)
+ device.WriteFeatureData(data);
+ }
+
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEO2.cs b/HandheldCompanion/Devices/AYANEO/AYANEO2.cs
index 2bcf2171f..73a4ce119 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEO2.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEO2.cs
@@ -5,7 +5,7 @@
namespace HandheldCompanion.Devices;
-public class AYANEO2 : IDevice
+public class AYANEO2 : AYANEO.AYANEODevice
{
public AYANEO2()
{
@@ -17,17 +17,18 @@ public AYANEO2()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 3, 33 };
GfxClock = new double[] { 100, 2200 };
+ CpuClock = 4700;
- AngularVelocityAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxis = new Vector3(1.0f, 1.0f, 1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(1.0f, 1.0f, 1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
@@ -36,15 +37,16 @@ public AYANEO2()
// device specific capacities
Capabilities = DeviceCapabilities.FanControl;
+ Capabilities |= DeviceCapabilities.DynamicLighting;
ECDetails = new ECDetails
{
- AddressControl = 0x44A,
- AddressDuty = 0x44B,
- AddressRegistry = 0x4E,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 100
+ AddressFanControl = 0x44A,
+ AddressFanDuty = 0x44B,
+ AddressStatusCommandPort = 0x4E,
+ AddressDataPort = 0x4F,
+ FanValueMin = 0,
+ FanValueMax = 100
};
OEMChords.Add(new DeviceChord("Custom Key Top Right",
@@ -71,4 +73,20 @@ public AYANEO2()
false, ButtonFlags.OEM2
));
}
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\uE003";
+ case ButtonFlags.OEM2:
+ return "\u220B";
+ case ButtonFlags.OEM3:
+ return "\u220A";
+ case ButtonFlags.OEM4:
+ return "\u2209";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEO2021.cs b/HandheldCompanion/Devices/AYANEO/AYANEO2021.cs
index 6feb5fab1..d75f622b3 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEO2021.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEO2021.cs
@@ -12,19 +12,20 @@ public AYANEO2021()
ProductIllustration = "device_aya_2021";
ProductModel = "AYANEO2021";
- // https://www.amd.com/fr/products/apu/amd-ryzen-5-4500u
+ // https://www.amd.com/en/support/apu/amd-ryzen-processors/amd-ryzen-5-mobile-processors-radeon-graphics/amd-ryzen-5-4500u
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 3, 25 };
- GfxClock = new double[] { 100, 1750 };
+ GfxClock = new double[] { 100, 1500 };
+ CpuClock = 4000;
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
@@ -53,4 +54,19 @@ public AYANEO2021()
false, ButtonFlags.OEM3
));
}
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\uE008";
+ case ButtonFlags.OEM2:
+ return "\u242F";
+ case ButtonFlags.OEM3:
+ return "\u243D";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEO2021Pro.cs b/HandheldCompanion/Devices/AYANEO/AYANEO2021Pro.cs
index a6ecd3f14..5508910dd 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEO2021Pro.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEO2021Pro.cs
@@ -5,53 +5,14 @@
namespace HandheldCompanion.Devices;
-public class AYANEO2021Pro : IDevice
+public class AYANEO2021Pro : AYANEO2021
{
public AYANEO2021Pro()
{
- // device specific settings
- ProductIllustration = "device_aya_2021";
- ProductModel = "AYANEO2021";
-
- // https://www.amd.com/fr/products/apu/amd-ryzen-7-4800u
+ // https://www.amd.com/en/support/apu/amd-ryzen-processors/amd-ryzen-7-mobile-processors-radeon-graphics/amd-ryzen-7-4800u
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 3, 25 };
GfxClock = new double[] { 100, 1750 };
-
- AngularVelocityAxisSwap = new SortedDictionary
- {
- { 'X', 'X' },
- { 'Y', 'Z' },
- { 'Z', 'Y' }
- };
-
- AccelerationAxisSwap = new SortedDictionary
- {
- { 'X', 'X' },
- { 'Y', 'Z' },
- { 'Z', 'Y' }
- };
-
- OEMChords.Add(new DeviceChord("WIN key",
- new List { KeyCode.LWin },
- new List { KeyCode.LWin },
- false, ButtonFlags.OEM1
- ));
-
- // Conflicts with OS
- //listeners.Add("TM key", new ChordClick(KeyCode.RAlt, KeyCode.RControlKey, KeyCode.Delete));
-
- OEMChords.Add(new DeviceChord("ESC key",
- new List { KeyCode.Escape },
- new List { KeyCode.Escape },
- false, ButtonFlags.OEM2
- ));
-
- // Conflicts with Ayaspace when installed
- OEMChords.Add(new DeviceChord("KB key",
- new List { KeyCode.RControlKey, KeyCode.LWin, KeyCode.O },
- new List { KeyCode.O, KeyCode.LWin, KeyCode.RControlKey },
- false, ButtonFlags.OEM3
- ));
+ CpuClock = 4200;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEO2S.cs b/HandheldCompanion/Devices/AYANEO/AYANEO2S.cs
index b08b6bcb1..3c739ff70 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEO2S.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEO2S.cs
@@ -1,74 +1,13 @@
-using HandheldCompanion.Inputs;
-using System.Collections.Generic;
-using System.Numerics;
-using WindowsInput.Events;
-
namespace HandheldCompanion.Devices;
-public class AYANEO2S : IDevice
+public class AYANEO2S : AYANEO2
{
public AYANEO2S()
{
- // device specific settings
- ProductIllustration = "device_aya_2";
- ProductModel = "AYANEO2";
-
// https://www.amd.com/en/products/apu/amd-ryzen-7-7840U
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 3, 30 };
GfxClock = new double[] { 100, 2700 };
-
- AngularVelocityAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
- {
- { 'X', 'X' },
- { 'Y', 'Z' },
- { 'Z', 'Y' }
- };
-
- AccelerationAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AccelerationAxisSwap = new SortedDictionary
- {
- { 'X', 'X' },
- { 'Y', 'Z' },
- { 'Z', 'Y' }
- };
-
- // device specific capacities
- Capabilities = DeviceCapabilities.FanControl;
-
- ECDetails = new ECDetails
- {
- AddressControl = 0x44A,
- AddressDuty = 0x44B,
- AddressRegistry = 0x4E,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 100
- };
-
- OEMChords.Add(new DeviceChord("Custom Key Top Right",
- new List { KeyCode.RControlKey, KeyCode.LWin, KeyCode.F16 },
- new List { KeyCode.F16, KeyCode.LWin, KeyCode.RControlKey },
- false, ButtonFlags.OEM3
- ));
-
- OEMChords.Add(new DeviceChord("Custom Key Top Left",
- new List { KeyCode.RControlKey, KeyCode.LWin, KeyCode.F15 },
- new List { KeyCode.F15, KeyCode.LWin, KeyCode.RControlKey },
- false, ButtonFlags.OEM4
- ));
-
- OEMChords.Add(new DeviceChord("Custom Key Big",
- new List { KeyCode.RControlKey, KeyCode.LWin, KeyCode.F17 },
- new List { KeyCode.F17, KeyCode.LWin, KeyCode.RControlKey },
- false, ButtonFlags.OEM1
- ));
-
- OEMChords.Add(new DeviceChord("Custom Key Small",
- new List { KeyCode.LWin, KeyCode.D },
- new List { KeyCode.LWin, KeyCode.D },
- false, ButtonFlags.OEM2
- ));
+ CpuClock = 5100;
}
}
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIR.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIR.cs
index d5f4ac66f..d07c76d40 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIR.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIR.cs
@@ -1,12 +1,10 @@
using HandheldCompanion.Inputs;
using System.Collections.Generic;
using System.Numerics;
-
using WindowsInput.Events;
-
namespace HandheldCompanion.Devices;
-public class AYANEOAIR : IDevice
+public class AYANEOAIR : AYANEO.AYANEODevice
{
public AYANEOAIR()
{
@@ -18,16 +16,17 @@ public AYANEOAIR()
nTDP = new double[] { 12, 12, 15 };
cTDP = new double[] { 3, 15 };
GfxClock = new double[] { 100, 1600 };
+ CpuClock = 4000;
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxis = new Vector3(-1.0f, 1.0f, -1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(-1.0f, 1.0f, -1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
@@ -36,15 +35,16 @@ public AYANEOAIR()
// device specific capacities
Capabilities = DeviceCapabilities.FanControl;
+ Capabilities |= DeviceCapabilities.DynamicLighting;
ECDetails = new ECDetails
{
- AddressControl = 0x44A,
- AddressDuty = 0x44B,
- AddressRegistry = 0x4E,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 100
+ AddressFanControl = 0x44A,
+ AddressFanDuty = 0x44B,
+ AddressStatusCommandPort = 0x4E,
+ AddressDataPort = 0x4F,
+ FanValueMin = 0,
+ FanValueMax = 100
};
OEMChords.Add(new DeviceChord("Custom Key Top Right",
@@ -71,4 +71,21 @@ public AYANEOAIR()
false, ButtonFlags.OEM2
));
}
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\uE003";
+ case ButtonFlags.OEM2:
+ return "\u220B";
+ case ButtonFlags.OEM3:
+ return "\u220A";
+ case ButtonFlags.OEM4:
+ return "\u2209";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIR1S.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIR1S.cs
index 5ae61d2e4..90aed0744 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIR1S.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIR1S.cs
@@ -12,6 +12,7 @@ public AYANEOAIR1S()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 4, 28 };
GfxClock = new double[] { 100, 2700 };
+ CpuClock = 5100;
OEMChords.Clear();
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIRLite.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIRLite.cs
index 9bf74600b..68ec80101 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIRLite.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIRLite.cs
@@ -8,5 +8,6 @@ public AYANEOAIRLite()
nTDP = new double[] { 8, 8, 12 };
cTDP = new double[] { 3, 12 };
GfxClock = new double[] { 100, 1600 };
+ CpuClock = 4000;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlus.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlus.cs
index 1c884aea2..feed5d1cb 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlus.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlus.cs
@@ -1,12 +1,15 @@
+using HandheldCompanion.Devices.AYANEO;
using HandheldCompanion.Inputs;
+using System;
using System.Collections.Generic;
using System.Numerics;
-
+using System.Windows.Media;
using WindowsInput.Events;
+using static HandheldCompanion.Utils.DeviceUtils;
namespace HandheldCompanion.Devices;
-public class AYANEOAIRPlus : IDevice
+public class AYANEOAIRPlus : AYANEODevice
{
public AYANEOAIRPlus()
{
@@ -14,22 +17,40 @@ public AYANEOAIRPlus()
ProductIllustration = "device_aya_air";
ProductModel = "AYANEOAir";
- AngularVelocityAxis = new Vector3(1.0f, -1.0f, -1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxis = new Vector3(1.0f, -1.0f, -1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxis = new Vector3(-1.0f, -1.0f, -1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(-1.0f, -1.0f, -1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
+ // device specific capacities
+ // todo, missing fan control
+ Capabilities |= DeviceCapabilities.DynamicLighting;
+ DynamicLightingCapabilities |= LEDLevel.SolidColor;
+ DynamicLightingCapabilities |= LEDLevel.Ambilight;
+
+ // Ayaneo Air Plus info based on:
+ // https://github.com/JustEnoughLinuxOS/distribution/blob/main/packages/hardware/quirks/devices/AYANEO%20AIR%20Plus/bin/ledcontrol
+ ECDetails = new ECDetails
+ {
+ AddressStatusCommandPort = 0x4E,
+ AddressDataPort = 0x4F,
+ AddressFanControl = 0x0, // Unknown
+ AddressFanDuty = 0x0, // Unknown
+ FanValueMin = 0, // Unknown
+ FanValueMax = 100 // Unknown
+ };
+
OEMChords.Add(new DeviceChord("Custom Key Top Right",
new List { KeyCode.LControl, KeyCode.LWin, KeyCode.F16 },
new List { KeyCode.F16, KeyCode.LControl, KeyCode.LWin },
@@ -54,4 +75,259 @@ public AYANEOAIRPlus()
false, ButtonFlags.OEM2
));
}
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\uE003";
+ case ButtonFlags.OEM2:
+ return "\u220B";
+ case ButtonFlags.OEM3:
+ return "\u220A";
+ case ButtonFlags.OEM4:
+ return "\u2209";
+ }
+
+ return defaultGlyph;
+ }
+
+ private void LedOpen()
+ {
+ ECRamDirectWrite(0x87, ECDetails, 0xA5);
+ }
+
+ private void LedClose(byte param)
+ {
+ ECRamDirectWrite(param, ECDetails, 0x01);
+ }
+
+ private void LedAck()
+ {
+ ECRamDirectWrite(0x70, ECDetails, 0x00);
+ LedClose(0x86);
+ }
+
+ private void LedEnable()
+ {
+ // Set LED ON
+ LedState(0x37);
+
+ LedOpen();
+ ECRamDirectWrite(0x70, ECDetails, 0x00);
+ LedClose(0x86);
+
+ LedOpen();
+ ECRamDirectWrite(0xB2, ECDetails, 0xBA);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0x72, ECDetails, 0xBA);
+ LedClose(0x86);
+
+ LedAck();
+ }
+
+ private void LedDisable()
+ {
+ // Set LED OFF
+ LedState(0x31);
+ }
+
+ private void LedApply()
+ {
+ LedOpen();
+ ECRamDirectWrite(0xBF, ECDetails, 0x00);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0x7F, ECDetails, 0x00);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0xC0, ECDetails, 0x00);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0x80, ECDetails, 0x00);
+ LedClose(0x86);
+
+ LedOpen();
+ ECRamDirectWrite(0xC1, ECDetails, 0x05);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0x81, ECDetails, 0x05);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0xC2, ECDetails, 0x05);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0x82, ECDetails, 0x05);
+ LedClose(0x86);
+
+ LedOpen();
+ ECRamDirectWrite(0xC3, ECDetails, 0x05);
+ LedClose(0x86);
+
+ LedOpen();
+ ECRamDirectWrite(0x83, ECDetails, 0x05);
+ LedClose(0x86);
+
+ LedOpen();
+ ECRamDirectWrite(0xC4, ECDetails, 0x05);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0x84, ECDetails, 0x05);
+ LedClose(0x86);
+
+ LedOpen();
+ ECRamDirectWrite(0xC5, ECDetails, 0x07);
+ LedClose(0xC6);
+
+ LedOpen();
+ ECRamDirectWrite(0x85, ECDetails, 0x07);
+ LedClose(0x86);
+
+ LedAck();
+ }
+
+ private void LedState(byte value)
+ {
+ // 0x31 = off
+ // 0x37 = on
+ byte[] zones = { 0xB2, 0x72 };
+
+ LedOpen();
+ foreach (byte zone in zones)
+ {
+ ECRamDirectWrite(zone, ECDetails, value);
+ ECRamDirectWrite(0xC6, ECDetails, 0x01);
+ }
+ LedAck();
+ }
+
+ private void SetLEDColorL(Color color)
+ {
+ byte red = color.R;
+ byte green = color.G;
+ byte blue = color.B;
+
+ LedOpen();
+ for (byte i = 0xB3; i <= 0xBC; i += 3)
+ {
+ ECRamDirectWrite(i, ECDetails, red);
+ ECRamDirectWrite((byte)(i + 1), ECDetails, green);
+ ECRamDirectWrite((byte)(i + 2), ECDetails, blue);
+ }
+ LedAck();
+ }
+
+ private void SetLEDColorR(Color color)
+ {
+ byte red = color.R;
+ byte green = color.G;
+ byte blue = color.B;
+
+ LedOpen();
+ for (byte i = 0x73; i <= 0x7C; i += 3)
+ {
+ ECRamDirectWrite(i, ECDetails, red);
+ ECRamDirectWrite((byte)(i + 1), ECDetails, green);
+ ECRamDirectWrite((byte)(i + 2), ECDetails, blue);
+ }
+ LedAck();
+ }
+
+ private void SetLEDColor(Color color)
+ {
+ SetLEDColorL(color);
+ SetLEDColorR(color);
+ }
+
+ public override bool ECRamDirectWrite(ushort address, ECDetails details, byte data)
+ {
+ ushort address2 = BitConverter.ToUInt16(new byte[] { (byte)address, 0xD1 }, 0);
+ return base.ECRamDirectWrite(address2, details, data);
+ }
+
+ public override bool SetLedStatus(bool status)
+ {
+ switch(status)
+ {
+ case true:
+ LedEnable();
+ break;
+ case false:
+ LedDisable();
+ break;
+ }
+
+ return true;
+ }
+
+ public override bool SetLedColor(Color MainColor, Color SecondaryColor, LEDLevel level, int speed)
+ {
+ if (!DynamicLightingCapabilities.HasFlag(level))
+ return false;
+
+ switch (level)
+ {
+ case LEDLevel.SolidColor:
+ SetLEDColor(MainColor);
+ break;
+ case LEDLevel.Ambilight:
+ SetLEDColorL(MainColor);
+ SetLEDColorR(SecondaryColor);
+ break;
+ }
+
+ LedApply();
+
+ return true;
+ }
+
+ public override bool SetLedBrightness(int brightness)
+ {
+ // we might want to store colors on SetLedColor() and brightness on SetLedBrightness()
+ // so that we can let people mess with brightness slider
+ return base.SetLedBrightness(brightness);
+ }
+
+ /*
+ private bool prevWasBlack = true;
+ public override bool SetLedColor(Color MainColor, Color SecondaryColor, LEDLevel level)
+ {
+ if (MainColor == Colors.Black)
+ {
+ ayaneoLED.LedDisable();
+ prevWasBlack = true;
+ return true;
+ }
+ else if (prevWasBlack)
+ {
+ ayaneoLED.LedEnable();
+ prevWasBlack = false;
+ }
+
+ // Set LED color
+ ayaneoLED.SetLEDColor(MainColor);
+ ayaneoLED.LedApply();
+
+ return true;
+ }
+
+ public bool SetAmbilight(Color LEDColor1, Color LEDColor2)
+ {
+ // Set LED color
+ ayaneoLED.SetLEDColorL(LEDColor1);
+ ayaneoLED.SetLEDColorR(LEDColor2);
+ ayaneoLED.LedApply();
+ return true;
+ }
+ */
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMD.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMD.cs
index a17c8eb97..d42631ca3 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMD.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMD.cs
@@ -8,5 +8,6 @@ public AYANEOAIRPlusAMD()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 3, 33 };
GfxClock = new double[] { 100, 2200 };
+ CpuClock = 4700;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMDMendocino.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMDMendocino.cs
index 1c2c75279..be7453c0a 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMDMendocino.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusAMDMendocino.cs
@@ -8,5 +8,6 @@ public AYANEOAIRPlusAMDMendocino()
nTDP = new double[] { 12, 12, 12 };
cTDP = new double[] { 5, 15 };
GfxClock = new double[] { 100, 1900 };
+ CpuClock = 4100;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusIntel.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusIntel.cs
index 3d36d5972..205af5181 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusIntel.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPlusIntel.cs
@@ -8,5 +8,6 @@ public AYANEOAIRPlusIntel()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 5, 55 };
GfxClock = new double[] { 100, 1100 };
+ CpuClock = 4400;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPro.cs b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPro.cs
index 7dd00fd56..803c97f54 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEOAIRPro.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEOAIRPro.cs
@@ -8,5 +8,6 @@ public AYANEOAIRPro()
nTDP = new double[] { 12, 12, 15 };
cTDP = new double[] { 3, 18 };
GfxClock = new double[] { 100, 2000 };
+ CpuClock = 4500;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs b/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs
new file mode 100644
index 000000000..3c17e3da4
--- /dev/null
+++ b/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs
@@ -0,0 +1,126 @@
+using HandheldCompanion.Managers;
+using HandheldCompanion.Utils;
+using System.Windows.Forms;
+using System.Windows.Media;
+using static HandheldCompanion.Utils.DeviceUtils;
+
+namespace HandheldCompanion.Devices.AYANEO
+{
+ public class AYANEODevice : IDevice
+ {
+ private enum JoystickSelection
+ {
+ Left = 1,
+ Right = 2,
+ Both = 3,
+ }
+
+ private byte[] zones = { 4, 1, 2, 3, 4 }; // Four zones per LED Ring, repeat first zone.
+ private byte maxIntensity = 100; // Use the max brightness for color brightness combination value
+
+ private int prevBatteryLevelPercentage;
+ private PowerStatus prevPowerStatus;
+
+ public AYANEODevice()
+ {
+ prevPowerStatus = SystemInformation.PowerStatus;
+ prevBatteryLevelPercentage = (int)(prevPowerStatus.BatteryLifePercent * 100);
+ PowerManager.PowerStatusChanged += PowerManager_PowerStatusChanged;
+ }
+
+ private void PowerManager_PowerStatusChanged(PowerStatus powerStatus)
+ {
+ // Ayaneo devices automatically set LED color and or effect in the following scenarios
+ // - Plugged in, charging
+ // - Fully charged, battery 100%
+ // - Battery almost empty, battery 5% or less
+ // This function overrides this change based on settings
+
+ // Get power information and bettery as a % of 100
+ int currentBatteryLevelPercentage = (int)(powerStatus.BatteryLifePercent * 100);
+
+ // Check if the device went from battery to charging
+ if (powerStatus.PowerLineStatus == PowerLineStatus.Online && prevPowerStatus.PowerLineStatus == PowerLineStatus.Offline)
+ {
+ LogManager.LogDebug("Ayaneo LED, device went from battery to charging, apply color");
+ base.PowerStatusChange(this);
+ }
+
+ // Check for the battery level change scenarios
+
+ // Check if the battery went from 99 or lower to 100
+ if (prevBatteryLevelPercentage <= 99 && currentBatteryLevelPercentage >= 100)
+ {
+ LogManager.LogDebug("Ayaneo LED, device went from < 99% battery to 100%, apply color");
+ base.PowerStatusChange(this);
+ }
+
+ // Check if the battery went from 6 or higher to 5 or lower
+ if (prevBatteryLevelPercentage >= 6 && currentBatteryLevelPercentage <= 5)
+ {
+ LogManager.LogDebug("Ayaneo LED, device went from > 5% battery <= 5%, apply color");
+ base.PowerStatusChange(this);
+ }
+
+ // Track battery level % and power status for next round
+ prevBatteryLevelPercentage = currentBatteryLevelPercentage;
+ prevPowerStatus = powerStatus;
+ }
+
+ private void SetJoystick(JoystickSelection joyStick)
+ {
+ ECRAMWrite(0x6d, (byte)joyStick);
+ }
+
+ public override bool SetLedColor(Color MainColor, Color SecondaryColor, LEDLevel level, int speed)
+ {
+ if (!DynamicLightingCapabilities.HasFlag(level))
+ return false;
+
+ switch (level)
+ {
+ case LEDLevel.SolidColor:
+ SetJoystick(JoystickSelection.Both);
+ SetLEDColor(MainColor);
+ break;
+ }
+
+ return true;
+ }
+
+ public override bool SetLedBrightness(int brightness)
+ {
+ // we might want to store colors on SetLedColor() and brightness on SetLedBrightness()
+ // so that we can let people mess with brightness slider
+ return base.SetLedBrightness(brightness);
+ }
+
+ private void SetLEDColor(Color color)
+ {
+ using (new ScopedLock(updateLock))
+ {
+ byte[] colorValues = { color.R, color.G, color.B };
+
+ for (byte zone = 0; zone < zones.Length; zone++)
+ {
+ // For R, G and B seperate. R = 0, G = 1, B = 2
+ for (byte colorComponentIndex = 0; colorComponentIndex < colorValues.Length; colorComponentIndex++)
+ {
+ byte zoneColorComponent = (byte)(zones[zone] * 3 + colorComponentIndex); // Indicates which Zone and which color component
+ byte colorComponentValueBrightness = (byte)(colorValues[colorComponentIndex] * maxIntensity / byte.MaxValue); // Convert 0-255 to 0-100
+
+ SetLED(zoneColorComponent, colorComponentValueBrightness);
+ }
+ }
+ }
+ }
+
+ private void SetLED(byte zoneColorComponent, byte colorComponentValueBrightness)
+ {
+ ECRAMWrite(0xbf, 0x10);
+ ECRAMWrite(0xb1, zoneColorComponent);
+ ECRAMWrite(0xb2, colorComponentValueBrightness);
+ ECRAMWrite(0xbf, 0xff);
+ }
+ }
+}
diff --git a/HandheldCompanion/Devices/AYANEO/AYANEONEXT.cs b/HandheldCompanion/Devices/AYANEO/AYANEONEXT.cs
index c44150878..0933991f6 100644
--- a/HandheldCompanion/Devices/AYANEO/AYANEONEXT.cs
+++ b/HandheldCompanion/Devices/AYANEO/AYANEONEXT.cs
@@ -18,15 +18,16 @@ public AYANEONEXT()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 10, 25 };
GfxClock = new double[] { 100, 2000 };
+ CpuClock = 4500;
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
@@ -45,4 +46,17 @@ public AYANEONEXT()
false, ButtonFlags.OEM2
));
}
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\uE003";
+ case ButtonFlags.OEM2:
+ return "\u220B";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/Ayn/AynLoki.cs b/HandheldCompanion/Devices/Ayn/AynLoki.cs
index e36326ab5..5d08a4311 100644
--- a/HandheldCompanion/Devices/Ayn/AynLoki.cs
+++ b/HandheldCompanion/Devices/Ayn/AynLoki.cs
@@ -1,49 +1,50 @@
using HandheldCompanion.Inputs;
+using System;
using System.Collections.Generic;
using System.Numerics;
+using System.Windows.Media;
using WindowsInput.Events;
-
+using static HandheldCompanion.Utils.DeviceUtils;
namespace HandheldCompanion.Devices;
public class AynLoki : IDevice
{
+ // Fan Control Mode.
+ // 0 Manual Mode
+ // 1 Automatic mode
+ // 2 User Defined Mode
+ private enum FanControlMode
+ {
+ Manual = 0,
+ Automatic = 1,
+ User = 2,
+ }
+
public AynLoki()
{
// Ayn Loki device generic settings
ProductIllustration = "device_ayn_loki";
ProductModel = "AynLoki";
- AngularVelocityAxis = new Vector3(1.0f, 1.0f, -1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxis = new Vector3(1.0f, 1.0f, -1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'Y' },
{ 'Y', 'Z' },
{ 'Z', 'X' }
};
- AccelerationAxis = new Vector3(1.0f, -1.0f, -1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(1.0f, -1.0f, -1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- // device specific capacities
- /*
- Capacities = DeviceCapacities.FanControl;
-
- ECDetails = new ECDetails
- {
- AddressControl = 0x12C,
- AddressDuty = 0x11,
- AddressRegistry = 0x12,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 128
- };
-
- */
+ // device specific capacities
+ Capabilities |= DeviceCapabilities.FanControl;
+ Capabilities |= DeviceCapabilities.DynamicLighting;
OEMChords.Add(new DeviceChord("Guide",
new List { KeyCode.LButton, KeyCode.XButton2 },
@@ -57,4 +58,119 @@ public AynLoki()
false, ButtonFlags.OEM2
));
}
+
+ public override void SetFanControl(bool enable, int mode)
+ {
+ //LogManager.LogDebug("AynLoki Set Fan Control {0}", enable);
+
+ // Define the ACPI memory address for fan control mode
+ byte ACPI_FanMode_Address = 0x10;
+
+ // Determine the fan control mode based enable
+ byte controlValue = enable ? (byte)FanControlMode.Manual : (byte)FanControlMode.Automatic;
+
+ // Update the fan control mode
+ ECRAMWrite(ACPI_FanMode_Address, controlValue);
+ }
+
+ public override void SetFanDuty(double percent)
+ {
+ //LogManager.LogDebug("AynLoki Set Fan Control Speed {0}%", percent);
+
+ // Fan control PWM value, range 0-128 (0 speed - 128 speed max)
+ byte ACPI_FanPWMDutyCycle_Address = 0x11;
+
+ // Convert 0-100 percentage to 0-128 range
+ byte fanSpeedSetpoint = (byte)(percent * 1.28);
+
+ // Ensure the value is within the valid range
+ fanSpeedSetpoint = Math.Min((byte)128, Math.Max((byte)0, fanSpeedSetpoint));
+
+ // Set the requested fan speed
+ ECRAMWrite(ACPI_FanPWMDutyCycle_Address, fanSpeedSetpoint);
+ }
+
+ public override float ReadFanDuty()
+ {
+ // Todo, untested and unverified
+
+ // Define ACPI memory addresses for fan speed data
+ byte ACPI_FanSpeed_5_Address = 0x20;
+ byte ACPI_FanTempe_5_Address = 0x21;
+
+ // Initialize the fan speed percentage
+ int fanSpeedPercentageActual = 0;
+
+ // Read the two bytes from memory (assumed to represent fan speed)
+ uint data1 = ECRamReadByte(ACPI_FanSpeed_5_Address);
+ uint data2 = ECRamReadByte(ACPI_FanTempe_5_Address);
+
+ //LogManager.LogDebug("AynLoki ReadFanDuty data1 {0} data2 {1}", data1, data2);
+
+ // Combine the two bytes into a 16-bit integer (fanSpeed)
+ short fanSpeed = (short)((data2 << 8) | data1);
+
+ // Assign the fan speed as a percentage to fanSpeedPercentageActual
+ fanSpeedPercentageActual = fanSpeed;
+
+ //LogManager.LogDebug("AynLoki ReadFanDuty percentage actual {0}", fanSpeedPercentageActual);
+
+ return fanSpeedPercentageActual;
+ }
+
+ public override bool SetLedColor(Color MainColor, Color SecondaryColor, LEDLevel level, int speed)
+ {
+ //LogManager.LogDebug("AynLoki Set LED color");
+
+ // Set LED color
+ byte PWM_R_Address = 0xB0; // PWM Red Duty cycle, range 0x00-0xFF
+ byte PWM_G_Address = 0xB1; // PWM Green Duty cycle, range 0x00-0xFF
+ byte PWM_B_Address = 0xB2; // PWM Blue Duty cycle, range 0x00-0xFF
+ byte LED_Control_mode_Address = 0xB3;
+ byte LED_Control_CompletedValue = 0x00;
+ byte LED_Control_Save = 0xAA; // Update request
+ byte LED_Control_RGB_Idle = 0x55; // This is in Ayn example code, not used in HC.
+
+ /*
+ 0x00, EC writes 0x00 to notify Host that the operation has been completed
+
+ 0xAA, Host writes 0xAA to inform EC that PWM_RGB needs to be updated.
+
+ Interaction logic: when 0xB3 is 0, Host can update the three values of PWM_RGB,
+ after updating, write 0xB3 to 0xAA to inform EC, after EC finished operation,
+ write 0xB3 to 0x01, Host write 0x01, that is, enter into auto-breathing.
+ */
+
+ // Todo, this ec write might not be required, code example and documentation from Ayn conflict
+ ECRAMWrite(LED_Control_mode_Address, LED_Control_Save);
+
+ uint LED_Control_Mode_Value = ECRamReadByte(LED_Control_mode_Address);
+
+ if (LED_Control_Mode_Value == LED_Control_CompletedValue)
+ {
+ // Update RGB addresses with respective values
+ ECRAMWrite(PWM_R_Address, MainColor.R);
+ ECRAMWrite(PWM_G_Address, MainColor.G);
+ ECRAMWrite(PWM_B_Address, MainColor.B);
+
+ ECRAMWrite(LED_Control_mode_Address, LED_Control_Save);
+
+ //LogManager.LogDebug("AynLoki Set LED write memory done");
+ }
+
+ return true;
+ }
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\u220C";
+ case ButtonFlags.OEM2:
+ return "\u220D";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/Ayn/AynLokiMax6600U.cs b/HandheldCompanion/Devices/Ayn/AynLokiMax6600U.cs
index e6211bd74..eea9a37fc 100644
--- a/HandheldCompanion/Devices/Ayn/AynLokiMax6600U.cs
+++ b/HandheldCompanion/Devices/Ayn/AynLokiMax6600U.cs
@@ -8,5 +8,6 @@ public LokiMax6600U()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 5, 28 };
GfxClock = new double[] { 100, 1900 };
+ CpuClock = 4500;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/Ayn/AynLokiMax6800U.cs b/HandheldCompanion/Devices/Ayn/AynLokiMax6800U.cs
index a8864d9a7..d4f0233ca 100644
--- a/HandheldCompanion/Devices/Ayn/AynLokiMax6800U.cs
+++ b/HandheldCompanion/Devices/Ayn/AynLokiMax6800U.cs
@@ -8,5 +8,6 @@ public LokiMax6800U()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 5, 28 };
GfxClock = new double[] { 100, 2200 };
+ CpuClock = 4700;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/Ayn/AynLokiMiniPro.cs b/HandheldCompanion/Devices/Ayn/AynLokiMiniPro.cs
index 9f8c9563b..af4c3820b 100644
--- a/HandheldCompanion/Devices/Ayn/AynLokiMiniPro.cs
+++ b/HandheldCompanion/Devices/Ayn/AynLokiMiniPro.cs
@@ -8,5 +8,6 @@ public LokiMiniPro()
nTDP = new double[] { 12, 12, 12 };
cTDP = new double[] { 5, 15 };
GfxClock = new double[] { 100, 1900 };
+ CpuClock = 4100;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/Ayn/AynLokiZero.cs b/HandheldCompanion/Devices/Ayn/AynLokiZero.cs
index 214454a36..4a9ea29a7 100644
--- a/HandheldCompanion/Devices/Ayn/AynLokiZero.cs
+++ b/HandheldCompanion/Devices/Ayn/AynLokiZero.cs
@@ -8,5 +8,6 @@ public LokiZero()
nTDP = new double[] { 10, 10, 10 };
cTDP = new double[] { 5, 15 };
GfxClock = new double[] { 100, 1100 };
+ CpuClock = 3200;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/CNCDan/NUCDeck.cs b/HandheldCompanion/Devices/CNCDan/NUCDeck.cs
new file mode 100644
index 000000000..6165367c4
--- /dev/null
+++ b/HandheldCompanion/Devices/CNCDan/NUCDeck.cs
@@ -0,0 +1,18 @@
+namespace HandheldCompanion.Devices;
+
+public class NUCDeck : IDevice
+{
+ public NUCDeck()
+ {
+ // device specific settings
+ ProductIllustration = "device_cncdan_nucdeck";
+ ProductModel = "NUCDeck";
+
+ // https://www.intel.com/content/www/us/en/products/sku/97539/intel-core-i57260u-processor-4m-cache-up-to-3-40-ghz/specifications.html
+ nTDP = new double[] { 15, 15, 15 };
+ cTDP = new double[] { 9, 15 };
+ GfxClock = new double[] { 300, 950 };
+ CpuClock = 3400;
+ }
+
+}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/GPD/GPDWin2.cs b/HandheldCompanion/Devices/GPD/GPDWin2.cs
index a882b412b..dc44f61c1 100644
--- a/HandheldCompanion/Devices/GPD/GPDWin2.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWin2.cs
@@ -11,5 +11,6 @@ public GPDWin2()
nTDP = new double[] { 10, 10, 15 };
cTDP = new double[] { 5, 15 };
GfxClock = new double[] { 300, 900 };
+ CpuClock = 3400;
}
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/GPD/GPDWin3.cs b/HandheldCompanion/Devices/GPD/GPDWin3.cs
index 7a357932b..00408b1eb 100644
--- a/HandheldCompanion/Devices/GPD/GPDWin3.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWin3.cs
@@ -15,6 +15,7 @@ public GPDWin3()
nTDP = new double[] { 20, 20, 25 };
cTDP = new double[] { 7, 25 };
GfxClock = new double[] { 100, 1400 };
+ CpuClock = 5000;
// note, need to manually configured in GPD app
OEMChords.Add(new DeviceChord("Bottom button left",
@@ -29,4 +30,17 @@ public GPDWin3()
false, ButtonFlags.OEM2
));
}
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\u220E";
+ case ButtonFlags.OEM2:
+ return "\u220F";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/GPD/GPDWin4-2023-7640U.cs b/HandheldCompanion/Devices/GPD/GPDWin4-2023-7640U.cs
index 9f0d0b341..d40511759 100644
--- a/HandheldCompanion/Devices/GPD/GPDWin4-2023-7640U.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWin4-2023-7640U.cs
@@ -1,74 +1,11 @@
-using HandheldCompanion.Inputs;
-using System.Collections.Generic;
-using System.Numerics;
-using WindowsInput.Events;
+namespace HandheldCompanion.Devices;
-namespace HandheldCompanion.Devices;
-
-public class GPDWin4_2023_7640U : IDevice
+public class GPDWin4_2023_7640U : GPDWin4_2023_7840U
{
public GPDWin4_2023_7640U()
{
- // device specific settings
- ProductIllustration = "device_gpd4";
-
// https://www.amd.com/en/products/apu/amd-ryzen-5-7640u
- nTDP = new double[] { 15, 15, 28 };
- cTDP = new double[] { 5, 28 };
GfxClock = new double[] { 200, 2600 };
-
- AngularVelocityAxis = new Vector3(1.0f, 1.0f, -1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
- {
- { 'X', 'Y' },
- { 'Y', 'Z' },
- { 'Z', 'X' }
- };
-
- AccelerationAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AccelerationAxisSwap = new SortedDictionary
- {
- { 'X', 'X' },
- { 'Y', 'Z' },
- { 'Z', 'Y' }
- };
-
- // device specific capacities
- Capabilities = DeviceCapabilities.FanControl;
-
- ECDetails = new ECDetails
- {
- AddressControl = 0x275,
- AddressDuty = 0x1809,
- AddressRegistry = 0x4E,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 184
- };
-
- // Note, OEM1 not configured as this device has it's own Menu button for guide button
-
- // Note, chords need to be manually configured in GPD app first by end user
-
- // GPD Back buttons do not have a "hold", configured buttons are key down and up immediately
- // Holding back buttons will result in same key down and up input every 2-3 seconds
- // Configured chords in GPD app need unique characters otherwise this leads to a
- // "mixed" result when pressing both buttons at the same time
- OEMChords.Add(new DeviceChord("Bottom button left",
- new List { KeyCode.F11, KeyCode.L },
- new List { KeyCode.F11, KeyCode.L },
- false, ButtonFlags.OEM2
- ));
-
- OEMChords.Add(new DeviceChord("Bottom button right",
- new List { KeyCode.F12, KeyCode.R },
- new List { KeyCode.F12, KeyCode.R },
- false, ButtonFlags.OEM3
- ));
- }
-
- public override void Close()
- {
- base.Close();
+ CpuClock = 4900;
}
}
diff --git a/HandheldCompanion/Devices/GPD/GPDWin4-2023-7840U.cs b/HandheldCompanion/Devices/GPD/GPDWin4-2023-7840U.cs
index ed97f046d..2f8010c47 100644
--- a/HandheldCompanion/Devices/GPD/GPDWin4-2023-7840U.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWin4-2023-7840U.cs
@@ -14,38 +14,39 @@ public GPDWin4_2023_7840U()
// https://www.amd.com/en/products/apu/amd-ryzen-7-7840u
nTDP = new double[] { 15, 15, 28 };
- cTDP = new double[] { 5, 28 };
+ cTDP = new double[] { 5, 30 };
GfxClock = new double[] { 200, 2700 };
+ CpuClock = 5100;
- AngularVelocityAxis = new Vector3(1.0f, 1.0f, -1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ // device specific capacities
+ Capabilities = DeviceCapabilities.FanControl;
+
+ ECDetails = new ECDetails
+ {
+ AddressFanControl = 0x275,
+ AddressFanDuty = 0x1809,
+ AddressStatusCommandPort = 0x4E,
+ AddressDataPort = 0x4F,
+ FanValueMin = 0,
+ FanValueMax = 184
+ };
+
+ GyrometerAxis = new Vector3(1.0f, 1.0f, -1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'Y' },
{ 'Y', 'Z' },
{ 'Z', 'X' }
};
- AccelerationAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(1.0f, 1.0f, 1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- // device specific capacities
- Capabilities = DeviceCapabilities.FanControl;
-
- ECDetails = new ECDetails
- {
- AddressControl = 0x275,
- AddressDuty = 0x1809,
- AddressRegistry = 0x4E,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 184
- };
-
// Note, OEM1 not configured as this device has it's own Menu button for guide button
// Note, chords need to be manually configured in GPD app first by end user
@@ -67,6 +68,19 @@ public GPDWin4_2023_7840U()
));
}
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM2:
+ return "\u220E";
+ case ButtonFlags.OEM3:
+ return "\u220F";
+ }
+
+ return defaultGlyph;
+ }
+
public override void Close()
{
base.Close();
diff --git a/HandheldCompanion/Devices/GPD/GPDWin4.cs b/HandheldCompanion/Devices/GPD/GPDWin4.cs
index 6276c7e94..f01c099a9 100644
--- a/HandheldCompanion/Devices/GPD/GPDWin4.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWin4.cs
@@ -18,29 +18,30 @@ public GPDWin4()
nTDP = new double[] { 15, 15, 28 };
cTDP = new double[] { 5, 28 };
GfxClock = new double[] { 100, 2200 };
+ CpuClock = 4700;
// device specific capacities
Capabilities = DeviceCapabilities.FanControl;
ECDetails = new ECDetails
{
- AddressControl = 0xC311,
- AddressDuty = 0xC880,
- AddressRegistry = 0x2E,
- AddressData = 0x2F,
- ValueMin = 0,
- ValueMax = 127
+ AddressFanControl = 0xC311,
+ AddressFanDuty = 0xC880,
+ AddressStatusCommandPort = 0x2E,
+ AddressDataPort = 0x2F,
+ FanValueMin = 0,
+ FanValueMax = 127
};
- AngularVelocityAxis = new Vector3(-1.0f, -1.0f, 1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxis = new Vector3(-1.0f, -1.0f, 1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
@@ -68,7 +69,20 @@ public GPDWin4()
));
}
- public override void SetFanControl(bool enable)
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM2:
+ return "\u220E";
+ case ButtonFlags.OEM3:
+ return "\u220F";
+ }
+
+ return defaultGlyph;
+ }
+
+ public override void SetFanControl(bool enable, int mode = 0)
{
switch (enable)
{
@@ -80,13 +94,13 @@ public override void SetFanControl(bool enable)
public override void SetFanDuty(double percent)
{
- if (ECDetails.AddressControl == 0)
+ if (ECDetails.AddressFanControl == 0)
return;
- var duty = percent * (ECDetails.ValueMax - ECDetails.ValueMin) / 100 + ECDetails.ValueMin;
+ var duty = percent * (ECDetails.FanValueMax - ECDetails.FanValueMin) / 100 + ECDetails.FanValueMin;
var data = Convert.ToByte(duty);
- ECRamDirectWrite(ECDetails.AddressControl, ECDetails, data);
+ ECRamDirectWrite(ECDetails.AddressFanControl, ECDetails, data);
}
public override bool Open()
diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2.cs
index 3fabc0b96..48a3a2a84 100644
--- a/HandheldCompanion/Devices/GPD/GPDWinMax2.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWinMax2.cs
@@ -16,12 +16,12 @@ public GPDWinMax2()
ECDetails = new ECDetails
{
- AddressControl = 0x275,
- AddressDuty = 0x1809,
- AddressRegistry = 0x4E,
- AddressData = 0x4F,
- ValueMin = 0,
- ValueMax = 184
+ AddressFanControl = 0x275,
+ AddressFanDuty = 0x1809,
+ AddressStatusCommandPort = 0x4E,
+ AddressDataPort = 0x4F,
+ FanValueMin = 0,
+ FanValueMax = 184
};
// Disabled this one as Win Max 2 also sends an Xbox guide input when Menu key is pressed.
@@ -44,4 +44,17 @@ public GPDWinMax2()
false, ButtonFlags.OEM3
));
}
+
+ public override string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM2:
+ return "\u220E";
+ case ButtonFlags.OEM3:
+ return "\u220F";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2AMD.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2AMD.cs
index b1840c945..27e7f7be7 100644
--- a/HandheldCompanion/Devices/GPD/GPDWinMax2AMD.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWinMax2AMD.cs
@@ -11,17 +11,18 @@ public GPDWinMax2AMD()
nTDP = new double[] { 15, 15, 28 };
cTDP = new double[] { 15, 28 };
GfxClock = new double[] { 100, 2200 };
+ CpuClock = 4700;
- AngularVelocityAxis = new Vector3(1.0f, 1.0f, -1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxis = new Vector3(1.0f, 1.0f, -1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'Y' },
{ 'Y', 'Z' },
{ 'Z', 'X' }
};
- AccelerationAxis = new Vector3(1.0f, -1.0f, 1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(1.0f, -1.0f, 1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2Intel.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2Intel.cs
index c6eb17727..e2f88f627 100644
--- a/HandheldCompanion/Devices/GPD/GPDWinMax2Intel.cs
+++ b/HandheldCompanion/Devices/GPD/GPDWinMax2Intel.cs
@@ -11,17 +11,18 @@ public GPDWinMax2Intel()
nTDP = new double[] { 15, 15, 20 };
cTDP = new double[] { 15, 28 };
GfxClock = new double[] { 100, 1400 };
+ CpuClock = 4700;
- AngularVelocityAxis = new Vector3(1.0f, -1.0f, 1.0f);
- AngularVelocityAxisSwap = new SortedDictionary
+ GyrometerAxis = new Vector3(1.0f, -1.0f, 1.0f);
+ GyrometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
{ 'Z', 'Y' }
};
- AccelerationAxis = new Vector3(1.0f, 1.0f, 1.0f);
- AccelerationAxisSwap = new SortedDictionary
+ AccelerometerAxis = new Vector3(1.0f, 1.0f, 1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
{
{ 'X', 'X' },
{ 'Y', 'Z' },
diff --git a/HandheldCompanion/Devices/IDevice.cs b/HandheldCompanion/Devices/IDevice.cs
index db844d11e..c078753c3 100644
--- a/HandheldCompanion/Devices/IDevice.cs
+++ b/HandheldCompanion/Devices/IDevice.cs
@@ -1,15 +1,25 @@
+using HandheldCompanion.Controls;
using HandheldCompanion.Inputs;
using HandheldCompanion.Managers;
+using HandheldCompanion.Misc;
using HandheldCompanion.Sensors;
using HandheldCompanion.Utils;
using HidLibrary;
+using Inkore.UI.WPF.Modern.Controls;
+using LibreHardwareMonitor.Hardware.Motherboard;
+using Nefarius.Utilities.DeviceManagement.PnP;
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.Linq;
using System.Numerics;
+using System.Threading;
+using System.Windows.Media;
using Windows.Devices.Sensors;
+using WindowsInput.Events;
using static HandheldCompanion.OneEuroFilter;
using static HandheldCompanion.OpenLibSys;
+using static HandheldCompanion.Utils.DeviceUtils;
namespace HandheldCompanion.Devices;
@@ -19,43 +29,61 @@ public enum DeviceCapabilities : ushort
None = 0,
InternalSensor = 1,
ExternalSensor = 2,
- FanControl = 4
+ FanControl = 4,
+ DynamicLighting = 8,
+ DynamicLightingBrightness = 16
}
public struct ECDetails
{
- public ushort AddressRegistry;
- public ushort AddressData;
- public ushort AddressControl;
- public ushort AddressDuty;
-
- public short ValueMin;
- public short ValueMax;
+ // Todo, remove comments
+ // ADDR_PORT="0x4e" <-- AddressStatusCommandPort should be called address port??
+ // DATA_PORT="0x4f" <-- AddressDataPort should be called data port??
+
+ // Ayaneo LED control calls them EC_Data and EC_SC
+ //private const uint EC_DATA = 0x62; // Data Port
+ //private const uint EC_SC = 0x66; // Status/Command Port
+
+ public ushort AddressStatusCommandPort; // Address of the register, In EC communication, the registry address specifies the type of data or command you want to access.
+ public ushort AddressDataPort; // Address where the data needs to go to, When interacting with the EC, the data address is where you send or receive the actual data or commands you want to communicate with the EC.
+ public ushort AddressFanControl; // Never used?
+ public ushort AddressFanDuty; // Never used?
+
+ // https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-register-descriptions.html
+ // The embedded controller contains three registers at two address locations: EC_SC and EC_DATA.
+ // EC_SC Status Command Register
+ // EC_DATA Data register
+
+ public short FanValueMin;
+ public short FanValueMax;
}
public abstract class IDevice
{
public delegate void KeyPressedEventHandler(ButtonFlags button);
public delegate void KeyReleasedEventHandler(ButtonFlags button);
+ public delegate void PowerStatusChangedEventHandler(IDevice device);
private static OpenLibSys openLibSys;
+ protected LockObject updateLock = new();
private static IDevice device;
protected ushort _vid, _pid;
+ public Dictionary hidDevices = new();
- public Vector3 AccelerationAxis = new(1.0f, 1.0f, 1.0f);
+ public Vector3 AccelerometerAxis = new(1.0f, 1.0f, 1.0f);
- public SortedDictionary AccelerationAxisSwap = new()
+ public SortedDictionary AccelerometerAxisSwap = new()
{
{ 'X', 'X' },
{ 'Y', 'Y' },
{ 'Z', 'Z' }
};
- public Vector3 AngularVelocityAxis = new(1.0f, 1.0f, 1.0f);
+ public Vector3 GyrometerAxis = new(1.0f, 1.0f, 1.0f);
- public SortedDictionary AngularVelocityAxisSwap = new()
+ public SortedDictionary GyrometerAxisSwap = new()
{
{ 'X', 'X' },
{ 'Y', 'Y' },
@@ -63,31 +91,66 @@ public abstract class IDevice
};
public DeviceCapabilities Capabilities = DeviceCapabilities.None;
+ public LEDLevel DynamicLightingCapabilities = LEDLevel.SolidColor;
+
+ protected const byte EC_OBF = 0x01; // Output Buffer Full
+ protected const byte EC_IBF = 0x02; // Input Buffer Full
+ protected const byte EC_DATA = 0x62; // Data Port
+ protected const byte EC_SC = 0x66; // Status/Command Port
+ protected const byte RD_EC = 0x80; // Read Embedded Controller
+ protected const byte WR_EC = 0x81; // Write Embedded Controller
// device configurable TDP (down, up)
public double[] cTDP = { 10, 25 };
- public ECDetails ECDetails;
-
- public string ExternalSensorName = string.Empty;
-
// device GfxClock frequency limits
public double[] GfxClock = { 100, 1800 };
- public string InternalSensorName = string.Empty;
+ public uint CpuClock = 6000;
// device nominal TDP (slow, fast)
public double[] nTDP = { 15, 15, 20 };
+ // device maximum operating temperature
+ public double Tjmax = 100;
+
+ // power profile(s)
+ // we might want to create an array instead
+ public PowerProfile powerProfileQuiet;
+ public PowerProfile powerProfileBalanced = new(Properties.Resources.PowerProfileDefaultName, Properties.Resources.PowerProfileDefaultDescription)
+ {
+ Default = true,
+ Guid = PowerMode.BetterPerformance,
+ OSPowerMode = PowerMode.BetterPerformance,
+ };
+ public PowerProfile powerProfileCool;
+
+ public List fanPresets = new()
+ {
+ // 00, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100�C
+ { new double[] { 20, 20, 20, 20, 20, 25, 30, 40, 70, 70, 100 } }, // Quiet
+ { new double[] { 20, 20, 20, 30, 40, 50, 70, 80, 90, 100, 100 } }, // Default
+ { new double[] { 40, 40, 40, 40, 40, 50, 70, 80, 90, 100, 100 } }, // Aggressive
+ };
+
// trigger specific settings
public List OEMChords = new();
// filter settings
public OneEuroSettings oneEuroSettings = new(0.002d, 0.008d);
+ // UI
+ protected FontFamily GlyphFontFamily = new("PromptFont");
+ protected const string defaultGlyph = "\u2753";
+
+ public ECDetails ECDetails;
+
+ public string ExternalSensorName = string.Empty;
+ public string InternalSensorName = string.Empty;
+
public string ProductIllustration = "device_generic";
public string ProductModel = "default";
- // mininum delay before trying to emulate a virtual controller on system resume (milliseconds)
+ // minimum delay before trying to emulate a virtual controller on system resume (milliseconds)
public short ResumeDelay = 1000;
// key press delay to use for certain scenarios
@@ -105,8 +168,11 @@ public IDevice()
public virtual bool IsSupported => true;
+ public Layout DefaultLayout { get; set; } = LayoutTemplate.DefaultLayout.Layout;
+
public event KeyPressedEventHandler KeyPressed;
public event KeyReleasedEventHandler KeyReleased;
+ public event PowerStatusChangedEventHandler PowerStatusChanged;
public string ManufacturerName = string.Empty;
public string ProductName = string.Empty;
@@ -120,11 +186,13 @@ public static IDevice GetDefault()
if (device is not null)
return device;
+ MotherboardInfo.UpdateMotherboard();
+
var ManufacturerName = MotherboardInfo.Manufacturer.ToUpper();
var ProductName = MotherboardInfo.Product;
var SystemName = MotherboardInfo.SystemName;
var Version = MotherboardInfo.Version;
- var Processor = MotherboardInfo.Processor;
+ var Processor = MotherboardInfo.ProcessorName;
var NumberOfCores = MotherboardInfo.NumberOfCores;
switch (ManufacturerName)
@@ -153,6 +221,7 @@ public static IDevice GetDefault()
}
}
break;
+
case "AOKZOE":
{
switch (ProductName)
@@ -166,6 +235,7 @@ public static IDevice GetDefault()
}
}
break;
+
case "AYADEVICE":
case "AYANEO":
{
@@ -217,6 +287,17 @@ public static IDevice GetDefault()
}
break;
+ case "CNCDAN":
+ {
+ switch (ProductName)
+ {
+ case "NucDeckRev1.0":
+ device = new NUCDeck();
+ break;
+ }
+ }
+ break;
+
case "GPD":
{
switch (ProductName)
@@ -256,6 +337,17 @@ public static IDevice GetDefault()
{
switch (ProductName)
{
+ case "ONEXPLAYER F1":
+ {
+ switch (Version)
+ {
+ default:
+ case "Default string":
+ device = new OneXPlayerOneXFly();
+ break;
+ }
+ break;
+ }
case "ONE XPLAYER":
case "ONEXPLAYER Mini Pro":
{
@@ -294,7 +386,7 @@ public static IDevice GetDefault()
{
default:
case "Version 1.0":
- device = new OneXPlayer2_7840U();
+ device = new OneXPlayer2Pro();
break;
}
break;
@@ -319,11 +411,23 @@ public static IDevice GetDefault()
switch (ProductName)
{
case "Jupiter":
+ case "Galileo":
device = new SteamDeck();
break;
}
}
break;
+
+ case "LENOVO":
+ {
+ switch (ProductName)
+ {
+ case "LNVNB161216":
+ device = new LegionGo();
+ break;
+ }
+ }
+ break;
}
LogManager.LogInformation("{0} from {1}", ProductName, ManufacturerName);
@@ -455,42 +559,58 @@ public bool RestartSensor()
return PnPUtil.RestartDevice(sensor.DeviceId);
}
- public string GetButtonName(ButtonFlags button)
- {
- return EnumUtils.GetDescriptionFromEnumValue(button, GetType().Name);
- }
-
public virtual void SetFanDuty(double percent)
{
- if (ECDetails.AddressDuty == 0)
+ if (ECDetails.AddressFanDuty == 0)
+ return;
+
+ if (!IsOpen)
return;
- var duty = percent * (ECDetails.ValueMax - ECDetails.ValueMin) / 100 + ECDetails.ValueMin;
+ var duty = percent * (ECDetails.FanValueMax - ECDetails.FanValueMin) / 100 + ECDetails.FanValueMin;
var data = Convert.ToByte(duty);
- ECRamDirectWrite(ECDetails.AddressDuty, ECDetails, data);
+ ECRamDirectWrite(ECDetails.AddressFanDuty, ECDetails, data);
}
- public virtual void SetFanControl(bool enable)
+ public virtual void SetFanControl(bool enable, int mode = 0)
{
- if (ECDetails.AddressControl == 0)
+ if (ECDetails.AddressFanControl == 0)
+ return;
+
+ if (!IsOpen)
return;
var data = Convert.ToByte(enable);
- ECRamDirectWrite(ECDetails.AddressControl, ECDetails, data);
+ ECRamDirectWrite(ECDetails.AddressFanControl, ECDetails, data);
}
public virtual float ReadFanDuty()
{
- if (ECDetails.AddressControl == 0)
+ if (ECDetails.AddressFanControl == 0)
return 0;
// todo: implement me
return 0;
}
+ public virtual bool SetLedStatus(bool status)
+ {
+ return true;
+ }
+
+ public virtual bool SetLedBrightness(int brightness)
+ {
+ return true;
+ }
+
+ public virtual bool SetLedColor(Color MainColor, Color SecondaryColor, LEDLevel level, int speed = 100)
+ {
+ return true;
+ }
+
[Obsolete("ECRamReadByte is deprecated, please use ECRamReadByte with ECDetails instead.")]
- public static byte ECRamReadByte(ushort address)
+ public virtual byte ECRamReadByte(ushort address)
{
try
{
@@ -504,28 +624,44 @@ public static byte ECRamReadByte(ushort address)
}
}
- public static byte ECRamReadByte(ushort address, ECDetails details)
+ [Obsolete("ECRamWriteByte is deprecated, please use ECRamDirectWrite with ECDetails instead.")]
+ public virtual bool ECRamWriteByte(ushort address, byte data)
+ {
+ try
+ {
+ openLibSys.WriteIoPortByte(address, data);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogManager.LogError("Couldn't write byte to address {0} using OpenLibSys. ErrorCode: {1}", address,
+ ex.Message);
+ return false;
+ }
+ }
+
+ public virtual byte ECRamReadByte(ushort address, ECDetails details)
{
var addr_upper = (byte)((address >> 8) & byte.MaxValue);
var addr_lower = (byte)(address & byte.MaxValue);
try
{
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2E);
- openLibSys.WriteIoPortByte(details.AddressData, 0x11);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2F);
- openLibSys.WriteIoPortByte(details.AddressData, addr_upper);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2E);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, 0x11);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2F);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, addr_upper);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2E);
- openLibSys.WriteIoPortByte(details.AddressData, 0x10);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2F);
- openLibSys.WriteIoPortByte(details.AddressData, addr_lower);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2E);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, 0x10);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2F);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, addr_lower);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2E);
- openLibSys.WriteIoPortByte(details.AddressData, 0x12);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2F);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2E);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, 0x12);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2F);
- return openLibSys.ReadIoPortByte(details.AddressData);
+ return openLibSys.ReadIoPortByte(details.AddressDataPort);
}
catch (Exception ex)
{
@@ -534,27 +670,27 @@ public static byte ECRamReadByte(ushort address, ECDetails details)
}
}
- public static bool ECRamDirectWrite(ushort address, ECDetails details, byte data)
+ public virtual bool ECRamDirectWrite(ushort address, ECDetails details, byte data)
{
- var addr_upper = (byte)((address >> 8) & byte.MaxValue);
- var addr_lower = (byte)(address & byte.MaxValue);
+ byte addr_upper = (byte)((address >> 8) & byte.MaxValue);
+ byte addr_lower = (byte)(address & byte.MaxValue);
try
{
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2E);
- openLibSys.WriteIoPortByte(details.AddressData, 0x11);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2F);
- openLibSys.WriteIoPortByte(details.AddressData, addr_upper);
-
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2E);
- openLibSys.WriteIoPortByte(details.AddressData, 0x10);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2F);
- openLibSys.WriteIoPortByte(details.AddressData, addr_lower);
-
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2E);
- openLibSys.WriteIoPortByte(details.AddressData, 0x12);
- openLibSys.WriteIoPortByte(details.AddressRegistry, 0x2F);
- openLibSys.WriteIoPortByte(details.AddressData, data);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2E);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, 0x11);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2F);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, addr_upper);
+
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2E);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, 0x10);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2F);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, addr_lower);
+
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2E);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, 0x12);
+ openLibSys.WriteIoPortByte(details.AddressStatusCommandPort, 0x2F);
+ openLibSys.WriteIoPortByte(details.AddressDataPort, data);
return true;
}
catch (Exception ex)
@@ -564,6 +700,37 @@ public static bool ECRamDirectWrite(ushort address, ECDetails details, byte data
}
}
+ protected void ECRAMWrite(byte address, byte data)
+ {
+ SendECCommand(WR_EC);
+ SendECData(address);
+ SendECData(data);
+ }
+
+ protected void SendECCommand(byte command)
+ {
+ if (IsECReady())
+ ECRamWriteByte(EC_SC, command);
+ }
+
+ protected void SendECData(byte data)
+ {
+ if (IsECReady())
+ ECRamWriteByte(EC_DATA, data);
+ }
+
+ protected bool IsECReady()
+ {
+ DateTime timeout = DateTime.Now.Add(TimeSpan.FromMilliseconds(50));
+ while (DateTime.Now < timeout && (ECRamReadByte(EC_SC) & EC_IBF) != 0x0)
+ Thread.Sleep(1);
+
+ if (DateTime.Now <= timeout)
+ return true;
+
+ return false;
+ }
+
protected void KeyPress(ButtonFlags button)
{
KeyPressed?.Invoke(button);
@@ -574,11 +741,122 @@ protected void KeyRelease(ButtonFlags button)
KeyReleased?.Invoke(button);
}
- protected static IEnumerable GetHidDevices(int vendorId, int deviceId, int minFeatures = 1)
+ public bool HasKey()
+ {
+ foreach (DeviceChord pair in OEMChords.Where(a => !a.silenced))
+ {
+ IEnumerable chords = pair.chords.SelectMany(chord => chord.Value);
+ if (chords.Any())
+ return true;
+ }
+
+ return false;
+ }
+
+ protected void ResumeDevices()
+ {
+ List successes = new();
+
+ StringCollection deviceInstanceIds = SettingsManager.GetStringCollection("SuspendedDevices");
+
+ if (deviceInstanceIds is null)
+ deviceInstanceIds = new();
+
+ foreach (string InstanceId in deviceInstanceIds)
+ {
+ if (PnPUtil.EnableDevice(InstanceId))
+ successes.Add(InstanceId);
+ }
+
+ foreach (string InstanceId in successes)
+ deviceInstanceIds.Remove(InstanceId);
+
+ SettingsManager.SetProperty("SuspendedDevices", deviceInstanceIds);
+ }
+
+ protected bool SuspendDevice(string InterfaceId)
+ {
+ PnPDevice pnPDevice = PnPDevice.GetDeviceByInterfaceId(InterfaceId);
+ if (pnPDevice is not null)
+ {
+ StringCollection deviceInstanceIds = SettingsManager.GetStringCollection("SuspendedDevices");
+
+ if (deviceInstanceIds is null)
+ deviceInstanceIds = new();
+
+ if (!deviceInstanceIds.Contains(pnPDevice.InstanceId))
+ deviceInstanceIds.Add(pnPDevice.InstanceId);
+
+ SettingsManager.SetProperty("SuspendedDevices", deviceInstanceIds);
+
+ return PnPUtil.DisableDevice(pnPDevice.InstanceId);
+ }
+
+ return false;
+ }
+
+ protected void PowerStatusChange(IDevice device)
+ {
+ PowerStatusChanged?.Invoke(device);
+ }
+
+ public static IEnumerable GetHidDevices(int vendorId, int deviceId, int minFeatures = 1)
{
HidDevice[] HidDeviceList = HidDevices.Enumerate(vendorId, new int[] { deviceId }).ToArray();
foreach (HidDevice device in HidDeviceList)
if (device.IsConnected && device.Capabilities.FeatureReportByteLength >= minFeatures)
yield return device;
}
+
+ public string GetButtonName(ButtonFlags button)
+ {
+ return EnumUtils.GetDescriptionFromEnumValue(button, GetType().Name);
+ }
+
+ public FontIcon GetFontIcon(ButtonFlags button, int FontIconSize = 14)
+ {
+ var FontIcon = new FontIcon
+ {
+ Glyph = GetGlyph(button),
+ FontSize = FontIconSize,
+ Foreground = null,
+ };
+
+ if (FontIcon.Glyph is not null)
+ {
+ FontIcon.FontFamily = GlyphFontFamily;
+ FontIcon.FontSize = 28;
+ }
+
+ return FontIcon;
+ }
+
+ public virtual string GetGlyph(ButtonFlags button)
+ {
+ switch (button)
+ {
+ case ButtonFlags.OEM1:
+ return "\u2780";
+ case ButtonFlags.OEM2:
+ return "\u2781";
+ case ButtonFlags.OEM3:
+ return "\u2782";
+ case ButtonFlags.OEM4:
+ return "\u2783";
+ case ButtonFlags.OEM5:
+ return "\u2784";
+ case ButtonFlags.OEM6:
+ return "\u2785";
+ case ButtonFlags.OEM7:
+ return "\u2786";
+ case ButtonFlags.OEM8:
+ return "\u2787";
+ case ButtonFlags.OEM9:
+ return "\u2788";
+ case ButtonFlags.OEM10:
+ return "\u2789";
+ }
+
+ return defaultGlyph;
+ }
}
\ No newline at end of file
diff --git a/HandheldCompanion/Devices/Lenovo/FanTable.cs b/HandheldCompanion/Devices/Lenovo/FanTable.cs
new file mode 100644
index 000000000..90a39c757
--- /dev/null
+++ b/HandheldCompanion/Devices/Lenovo/FanTable.cs
@@ -0,0 +1,89 @@
+using System;
+using System.IO;
+
+namespace HandheldCompanion.Devices.Lenovo
+{
+ public readonly struct FanTable
+ {
+ // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
+ // ReSharper disable MemberCanBePrivate.Global
+ // ReSharper disable IdentifierTypo
+ // ReSharper disable InconsistentNaming
+
+ public byte FSTM { get; init; }
+ public byte FSID { get; init; }
+ public uint FSTL { get; init; }
+ public ushort FSS0 { get; init; }
+ public ushort FSS1 { get; init; }
+ public ushort FSS2 { get; init; }
+ public ushort FSS3 { get; init; }
+ public ushort FSS4 { get; init; }
+ public ushort FSS5 { get; init; }
+ public ushort FSS6 { get; init; }
+ public ushort FSS7 { get; init; }
+ public ushort FSS8 { get; init; }
+ public ushort FSS9 { get; init; }
+
+ // ReSharper restore AutoPropertyCanBeMadeGetOnly.Global
+ // ReSharper restore MemberCanBePrivate.Global
+ // ReSharper restore IdentifierTypo
+ // ReSharper restore InconsistentNaming
+
+ public FanTable(ushort[] fanTable)
+ {
+ if (fanTable.Length != 10)
+ // ReSharper disable once LocalizableElement
+ throw new ArgumentException("Fan table length must be 10.", nameof(fanTable));
+
+ FSTM = 1;
+ FSID = 0;
+ FSTL = 0;
+ FSS0 = fanTable[0];
+ FSS1 = fanTable[1];
+ FSS2 = fanTable[2];
+ FSS3 = fanTable[3];
+ FSS4 = fanTable[4];
+ FSS5 = fanTable[5];
+ FSS6 = fanTable[6];
+ FSS7 = fanTable[7];
+ FSS8 = fanTable[8];
+ FSS9 = fanTable[9];
+ }
+
+ public ushort[] GetTable() => new[] { FSS0, FSS1, FSS2, FSS3, FSS4, FSS5, FSS6, FSS7, FSS8, FSS9 };
+
+ public byte[] GetBytes()
+ {
+ using var ms = new MemoryStream(new byte[64]);
+ ms.WriteByte(FSTM);
+ ms.WriteByte(FSID);
+ ms.Write(BitConverter.GetBytes(FSTL));
+ ms.Write(BitConverter.GetBytes(FSS0));
+ ms.Write(BitConverter.GetBytes(FSS1));
+ ms.Write(BitConverter.GetBytes(FSS2));
+ ms.Write(BitConverter.GetBytes(FSS3));
+ ms.Write(BitConverter.GetBytes(FSS4));
+ ms.Write(BitConverter.GetBytes(FSS5));
+ ms.Write(BitConverter.GetBytes(FSS6));
+ ms.Write(BitConverter.GetBytes(FSS7));
+ ms.Write(BitConverter.GetBytes(FSS8));
+ ms.Write(BitConverter.GetBytes(FSS9));
+ return ms.ToArray();
+ }
+
+ public override string ToString() =>
+ $"{nameof(FSTM)}: {FSTM}," +
+ $" {nameof(FSID)}: {FSID}," +
+ $" {nameof(FSTL)}: {FSTL}," +
+ $" {nameof(FSS0)}: {FSS0}," +
+ $" {nameof(FSS1)}: {FSS1}," +
+ $" {nameof(FSS2)}: {FSS2}," +
+ $" {nameof(FSS3)}: {FSS3}," +
+ $" {nameof(FSS4)}: {FSS4}," +
+ $" {nameof(FSS5)}: {FSS5}," +
+ $" {nameof(FSS6)}: {FSS6}," +
+ $" {nameof(FSS7)}: {FSS7}," +
+ $" {nameof(FSS8)}: {FSS8}," +
+ $" {nameof(FSS9)}: {FSS9}";
+ }
+}
diff --git a/HandheldCompanion/Devices/Lenovo/LegionGo.cs b/HandheldCompanion/Devices/Lenovo/LegionGo.cs
new file mode 100644
index 000000000..ec6821aad
--- /dev/null
+++ b/HandheldCompanion/Devices/Lenovo/LegionGo.cs
@@ -0,0 +1,361 @@
+using HandheldCompanion.Actions;
+using HandheldCompanion.Devices.Lenovo;
+using HandheldCompanion.Inputs;
+using HandheldCompanion.Managers;
+using HandheldCompanion.Misc;
+using HidLibrary;
+using Nefarius.Utilities.DeviceManagement.PnP;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management;
+using System.Numerics;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Media;
+using WindowsInput.Events;
+using static HandheldCompanion.Devices.Lenovo.SapientiaUsb;
+using static HandheldCompanion.Utils.DeviceUtils;
+
+namespace HandheldCompanion.Devices;
+
+public class LegionGo : IDevice
+{
+ public enum LegionMode
+ {
+ Quiet = 0x01,
+ Balanced = 0x02,
+ Performance = 0x03,
+ Custom = 0xFF,
+ }
+
+ private FanTable fanTable = new();
+
+ public const byte INPUT_HID_ID = 0x04;
+
+ public override bool IsOpen => hidDevices.ContainsKey(INPUT_HID_ID) && hidDevices[INPUT_HID_ID].IsOpen;
+
+ public LegionGo()
+ {
+ // device specific settings
+ ProductIllustration = "device_legion_go";
+
+ // used to monitor OEM specific inputs
+ _vid = 0x17EF;
+ _pid = 0x6182;
+
+ // https://www.amd.com/en/products/apu/amd-ryzen-z1
+ // https://www.amd.com/en/products/apu/amd-ryzen-z1-extreme
+ // https://www.amd.com/en/products/apu/amd-ryzen-7-7840u
+ nTDP = new double[] { 15, 15, 20 };
+ cTDP = new double[] { 5, 30 };
+ GfxClock = new double[] { 100, 2700 };
+ CpuClock = 5100;
+
+ GyrometerAxis = new Vector3(-1.0f, -1.0f, 1.0f);
+ GyrometerAxisSwap = new SortedDictionary
+ {
+ { 'X', 'X' },
+ { 'Y', 'Z' },
+ { 'Z', 'Y' }
+ };
+
+ AccelerometerAxis = new Vector3(-1.0f, 1.0f, -1.0f);
+ AccelerometerAxisSwap = new SortedDictionary
+ {
+ { 'X', 'X' },
+ { 'Y', 'Z' },
+ { 'Z', 'Y' }
+ };
+
+ // device specific capacities
+ Capabilities |= DeviceCapabilities.None;
+ // Capabilities |= DeviceCapabilities.FanControl;
+ Capabilities |= DeviceCapabilities.DynamicLighting;
+ Capabilities |= DeviceCapabilities.DynamicLightingBrightness;
+
+ // dynamic lighting capacities
+ DynamicLightingCapabilities |= LEDLevel.SolidColor;
+ DynamicLightingCapabilities |= LEDLevel.Ambilight;
+
+ powerProfileQuiet = new(Properties.Resources.PowerProfileSilentName, Properties.Resources.PowerProfileSilentDescription)
+ {
+ Default = true,
+ OSPowerMode = PowerMode.BetterBattery,
+ OEMPowerMode = (int)LegionMode.Quiet,
+ Guid = PowerMode.BetterBattery
+ };
+
+ powerProfileBalanced = new(Properties.Resources.PowerProfilePerformanceName, Properties.Resources.PowerProfilePerformanceDescription)
+ {
+ Default = true,
+ OSPowerMode = PowerMode.BetterPerformance,
+ OEMPowerMode = (int)LegionMode.Balanced,
+ Guid = PowerMode.BetterPerformance
+ };
+
+ powerProfileCool = new(Properties.Resources.PowerProfileTurboName, Properties.Resources.PowerProfileTurboDescription)
+ {
+ Default = true,
+ OSPowerMode = PowerMode.BestPerformance,
+ OEMPowerMode = (int)LegionMode.Performance,
+ Guid = PowerMode.BestPerformance
+ };
+
+ PowerProfileManager.Applied += PowerProfileManager_Applied;
+
+ OEMChords.Add(new DeviceChord("LegionR",
+ new List(), new List(),
+ false, ButtonFlags.OEM1
+ ));
+
+ OEMChords.Add(new DeviceChord("LegionL",
+ new List(), new List(),
+ false, ButtonFlags.OEM2
+ ));
+
+ // device specific layout
+ DefaultLayout.AxisLayout[AxisLayoutFlags.RightPad] = new MouseActions {MouseType = MouseActionsType.Move, Filtering = true, Sensivity = 15 };
+
+ DefaultLayout.ButtonLayout[ButtonFlags.RightPadClick] = new List() { new MouseActions { MouseType = MouseActionsType.LeftButton, HapticMode = HapticMode.Down, HapticStrength = HapticStrength.Low } };
+ DefaultLayout.ButtonLayout[ButtonFlags.RightPadClickDown] = new List() { new MouseActions { MouseType = MouseActionsType.RightButton, HapticMode = HapticMode.Down, HapticStrength = HapticStrength.High } };
+ DefaultLayout.ButtonLayout[ButtonFlags.B5] = new List() { new ButtonActions { Button = ButtonFlags.R1 } };
+ DefaultLayout.ButtonLayout[ButtonFlags.B6] = new List() { new MouseActions { MouseType = MouseActionsType.MiddleButton } };
+ DefaultLayout.ButtonLayout[ButtonFlags.B7] = new List() { new MouseActions { MouseType = MouseActionsType.ScrollUp } };
+ DefaultLayout.ButtonLayout[ButtonFlags.B8] = new List() { new MouseActions { MouseType = MouseActionsType.ScrollDown } };
+
+ Init();
+ }
+
+ public override void SetFanControl(bool enable, int mode = 0)
+ {
+ // do something
+ }
+
+ public override void SetFanDuty(double percent)
+ {
+ // do something
+ }
+
+ private void PowerProfileManager_Applied(PowerProfile profile, UpdateSource source)
+ {
+ if (profile.FanProfile.fanMode == FanMode.Hardware)
+ fanTable = new(new ushort[] { 44, 48, 55, 60, 71, 79, 87, 87, 100, 100 });
+ else
+ fanTable = new(new ushort[] {
+ (ushort)profile.FanProfile.fanSpeeds[1],
+ (ushort)profile.FanProfile.fanSpeeds[2],
+ (ushort)profile.FanProfile.fanSpeeds[3],
+ (ushort)profile.FanProfile.fanSpeeds[4],
+ (ushort)profile.FanProfile.fanSpeeds[5],
+ (ushort)profile.FanProfile.fanSpeeds[6],
+ (ushort)profile.FanProfile.fanSpeeds[7],
+ (ushort)profile.FanProfile.fanSpeeds[8],
+ (ushort)profile.FanProfile.fanSpeeds[9],
+ (ushort)profile.FanProfile.fanSpeeds[10],
+ });
+
+ try
+ {
+ // Fan control
+ ManagementScope managementScope = new ManagementScope("root\\WMI");
+ managementScope.Connect();
+ ObjectQuery objectQuery = new ObjectQuery("SELECT * FROM LENOVO_FAN_METHOD");
+ using (ManagementObjectCollection searcher = new ManagementObjectSearcher(managementScope, objectQuery).Get())
+ {
+ var obj = searcher.Cast