diff --git a/HandheldCompanion.iss b/HandheldCompanion.iss index 82349543c..41ee38364 100644 --- a/HandheldCompanion.iss +++ b/HandheldCompanion.iss @@ -8,6 +8,7 @@ #define UseNetCoreCheck #ifdef UseNetCoreCheck #define UseDotNet80 + ;#define UseDotNet90 #endif ;#define UseVC2005 @@ -26,7 +27,7 @@ #define InstallerVersion '0.2' #define MyAppSetupName 'Handheld Companion' #define MyBuildId 'HandheldCompanion' -#define MyAppVersion '0.21.6.1' +#define MyAppVersion '0.21.7.0' #define MyAppPublisher 'BenjaminLSR' #define MyAppCopyright 'Copyright @ BenjaminLSR' #define MyAppURL 'https://github.com/Valkirie/HandheldCompanion' @@ -52,12 +53,6 @@ ; RTSS 7.3.6 #define NewRtssVersion "7.3.5.28010" -//#define DotNetX64DownloadLink "https://download.visualstudio.microsoft.com/download/pr/b280d97f-25a9-4ab7-8a12-8291aa3af117/a37ed0e68f51fcd973e9f6cb4f40b1a7/windowsdesktop-runtime-8.0.0-win-x64.exe" -//#define DotNetX86DownloadLink "https://download.visualstudio.microsoft.com/download/pr/f9e3b581-059d-429f-9f0d-1d1167ff7e32/bd7661030cd5d66cd3eee0fd20b24540/windowsdesktop-runtime-8.0.0-win-x86.exe" - -#define DotNetX64DownloadLink "https://download.visualstudio.microsoft.com/download/pr/f18288f6-1732-415b-b577-7fb46510479a/a98239f751a7aed31bc4aa12f348a9bf/windowsdesktop-runtime-8.0.1-win-x64.exe" -#define DotNetX86DownloadLink "https://download.visualstudio.microsoft.com/download/pr/ca725693-6de7-4a4d-b8a4-4390b0387c66/ce13f2f016152d9b5f2d3c6537cc415b/windowsdesktop-runtime-8.0.1-win-x86.exe" - #define DirectXDownloadLink "https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe" #define HidHideDownloadLink "https://github.com/nefarius/HidHide/releases/download/v1.5.230.0/HidHide_1.5.230_x64.exe" #define ViGemDownloadLink "https://github.com/nefarius/ViGEmBus/releases/download/v1.22.0/ViGEmBus_1.22.0_x64_x86_arm64.exe" @@ -69,6 +64,14 @@ #ifdef UseDotNet80 #define MyConfigurationExt "net8.0" + #define DotNetX64DownloadLink "https://download.visualstudio.microsoft.com/download/pr/f18288f6-1732-415b-b577-7fb46510479a/a98239f751a7aed31bc4aa12f348a9bf/windowsdesktop-runtime-8.0.1-win-x64.exe" + #define DotNetX86DownloadLink "https://download.visualstudio.microsoft.com/download/pr/ca725693-6de7-4a4d-b8a4-4390b0387c66/ce13f2f016152d9b5f2d3c6537cc415b/windowsdesktop-runtime-8.0.1-win-x86.exe" +#endif + +#ifdef UseDotNet90 + #define MyConfigurationExt "net9.0" + #define DotNetX64DownloadLink "https://download.visualstudio.microsoft.com/download/pr/30d1fcdb-8bf1-4b6e-ad06-f66ed68017bf/20abf38df849587b0a2de99a31f5c1c8/windowsdesktop-runtime-9.0.0-rc.2.24474.4-win-x64.exe" + #define DotNetX86DownloadLink "https://download.visualstudio.microsoft.com/download/pr/f6a4c462-a2a6-4488-9448-574b659c31e5/7eb8840cb5e42e0fd41a57964fe3472c/windowsdesktop-runtime-9.0.0-rc.2.24474.4-win-x86.exe" #endif #define WindowsVersion "10.0.19041" @@ -169,6 +172,7 @@ procedure Dependency_Add_With_Version(const Filename, NewVersion, InstalledVersi function Dependency_PrepareToInstall(var NeedsRestart: Boolean): String; forward; function Dependency_UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String; forward; procedure Dependency_AddDotNet80Desktop; forward; +procedure Dependency_AddDotNet90Desktop; forward; procedure Dependency_AddDirectX; forward; procedure Dependency_AddHideHide; forward; procedure Dependency_AddViGem; forward; @@ -306,6 +310,15 @@ begin end; #endif +#ifdef UseDotNet90 + installedVersion:= regGetInstalledVersion('{#DotNetName}'); + if(compareVersions('{#NewDotNetVersion}', installedVersion, '.', '-') > 0) then + begin + log('{#DotNetName} {#NewDotNetVersion} needs update.'); + Dependency_AddDotNet90Desktop; + end; +#endif + #ifdef UseVC2005 Dependency_AddVC2005; #endif @@ -621,8 +634,17 @@ begin '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', '{#DotNetName}', Dependency_String('{#DotNetX86DownloadLink}', '{#DotNetX64DownloadLink}'), '', False, False); end; -end; +end; +procedure Dependency_AddDotNet90Desktop; +begin + // https://dotnet.microsoft.com/fr-fr/download/dotnet/9.0 + if not Dependency_IsNetCoreInstalled('Microsoft.WindowsDesktop.App 9.0.0') then begin + Dependency_Add_With_Version('dotNet90desktop' + Dependency_ArchSuffix + '.exe', '{#NewDotNetVersion}', regGetInstalledVersion('{#DotNetName}'), + '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart', + '{#DotNetName}', Dependency_String('{#DotNetX86DownloadLink}', '{#DotNetX64DownloadLink}'), '', False, False); + end; +end; procedure Dependency_AddVC2005; begin diff --git a/HandheldCompanion/Actions/MouseActions.cs b/HandheldCompanion/Actions/MouseActions.cs index 97fdc8c00..dfcdc52bc 100644 --- a/HandheldCompanion/Actions/MouseActions.cs +++ b/HandheldCompanion/Actions/MouseActions.cs @@ -51,7 +51,7 @@ public class MouseActions : GyroActions // settings axis public int Sensivity = 33; public float Acceleration = 1.0f; - public int Deadzone = 10; // stick only + public int Deadzone = 15; // stick only public bool Filtering = false; // pad only public float FilterCutoff = 0.05f; // pad only public bool AxisRotated = false; diff --git a/HandheldCompanion/App.config b/HandheldCompanion/App.config index 40c87ebd8..69abc7893 100644 --- a/HandheldCompanion/App.config +++ b/HandheldCompanion/App.config @@ -69,7 +69,7 @@ en-US - 0 + 20 False @@ -305,6 +305,9 @@ 100 + + False + \ No newline at end of file diff --git a/HandheldCompanion/Commands/EmptyCommands.cs b/HandheldCompanion/Commands/EmptyCommands.cs index f45fc32e3..4e847c913 100644 --- a/HandheldCompanion/Commands/EmptyCommands.cs +++ b/HandheldCompanion/Commands/EmptyCommands.cs @@ -12,9 +12,9 @@ public EmptyCommands() base.OnKeyDown = true; } - public virtual void Execute(bool IsKeyDown, bool IsKeyUp) + public virtual void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, IsBackground); } } } diff --git a/HandheldCompanion/Commands/ExecutableCommands.cs b/HandheldCompanion/Commands/ExecutableCommands.cs index 51f55c522..8b1c694ed 100644 --- a/HandheldCompanion/Commands/ExecutableCommands.cs +++ b/HandheldCompanion/Commands/ExecutableCommands.cs @@ -23,7 +23,7 @@ public ExecutableCommands() base.OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { if (!File.Exists(this.Path)) return; @@ -45,7 +45,7 @@ public override void Execute(bool IsKeyDown, bool IsKeyUp) process.Start(); }); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/FunctionCommands.cs b/HandheldCompanion/Commands/FunctionCommands.cs index f4f0cbc8e..c52efc3c4 100644 --- a/HandheldCompanion/Commands/FunctionCommands.cs +++ b/HandheldCompanion/Commands/FunctionCommands.cs @@ -1,5 +1,7 @@ using HandheldCompanion.Commands.Functions.HC; using HandheldCompanion.Commands.Functions.Multimedia; +using HandheldCompanion.Commands.Functions.Multitasking; +using HandheldCompanion.Commands.Functions.Performance; using HandheldCompanion.Commands.Functions.Windows; using System; using System.Collections.Generic; @@ -19,18 +21,28 @@ public class FunctionCommands : ICommands typeof(HIDModeCommands), typeof(DesktopLayoutCommands), typeof(CycleSubProfileCommands), + typeof(QuickOverlayCommands), + "Power & battery", + typeof(TDPIncrease), + typeof(TDPDecrease), "Windows", typeof(OnScreenKeyboardCommands), typeof(OnScreenKeyboardLegacyCommands), - typeof(KillForegroundCommands), typeof(ActionCenterCommands), typeof(SettingsCommands), typeof(ScreenshotCommands), - "Multimedia", + typeof(GameBarCommands), + "Multitasking", + typeof(KillForegroundCommands), + typeof(TaskManagerCommands), + typeof(SwapScreenCommands), + "Display", typeof(BrightnessIncrease), typeof(BrightnessDecrease), + "Sound", typeof(VolumeIncrease), typeof(VolumeDecrease), + typeof(VolumeMute), ]; public FunctionCommands() @@ -38,9 +50,9 @@ public FunctionCommands() base.commandType = CommandType.Function; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, IsBackground); } } } diff --git a/HandheldCompanion/Commands/Functions/HC/CycleSubProfileCommands.cs b/HandheldCompanion/Commands/Functions/HC/CycleSubProfileCommands.cs index 2eb4d32c4..84cfa8310 100644 --- a/HandheldCompanion/Commands/Functions/HC/CycleSubProfileCommands.cs +++ b/HandheldCompanion/Commands/Functions/HC/CycleSubProfileCommands.cs @@ -16,7 +16,7 @@ public CycleSubProfileCommands() base.OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { switch (CycleIndex) { @@ -28,7 +28,7 @@ public override void Execute(bool IsKeyDown, bool IsKeyUp) break; } - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/HC/DesktopLayoutCommands.cs b/HandheldCompanion/Commands/Functions/HC/DesktopLayoutCommands.cs index 9f805ca24..57bffa663 100644 --- a/HandheldCompanion/Commands/Functions/HC/DesktopLayoutCommands.cs +++ b/HandheldCompanion/Commands/Functions/HC/DesktopLayoutCommands.cs @@ -18,22 +18,22 @@ public DesktopLayoutCommands() SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { case SettingsName: - base.Execute(OnKeyDown, OnKeyUp); + base.Execute(OnKeyDown, OnKeyUp, true); break; } } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { bool value = !SettingsManager.GetBoolean(SettingsName, true); SettingsManager.SetProperty(SettingsName, value, false, true); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override bool IsToggled => SettingsManager.GetBoolean(SettingsName, true); diff --git a/HandheldCompanion/Commands/Functions/HC/HIDModeCommands.cs b/HandheldCompanion/Commands/Functions/HC/HIDModeCommands.cs index 78d5f9749..3628990e9 100644 --- a/HandheldCompanion/Commands/Functions/HC/HIDModeCommands.cs +++ b/HandheldCompanion/Commands/Functions/HC/HIDModeCommands.cs @@ -17,6 +17,19 @@ public HIDModeCommands() base.FontFamily = "PromptFont"; base.Glyph = "\u243C"; + Update(); + + ProfileManager.Applied += ProfileManager_Applied; + } + + private void ProfileManager_Applied(Profile profile, UpdateSource source) + { + IsEnabled = profile.HID == HIDmode.NotSelected; + Update(); + } + + public override void Update() + { HIDmode currentHIDmode = (HIDmode)SettingsManager.GetInt(SettingsName, true); switch (currentHIDmode) { @@ -28,16 +41,10 @@ public HIDModeCommands() break; } - ProfileManager.Applied += ProfileManager_Applied; - } - - private void ProfileManager_Applied(Profile profile, UpdateSource source) - { - IsEnabled = profile.HID == HIDmode.NotSelected; base.Update(); } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { if (IsEnabled) { @@ -46,19 +53,17 @@ public override void Execute(bool IsKeyDown, bool IsKeyUp) { case HIDmode.Xbox360Controller: SettingsManager.SetProperty(SettingsName, (int)HIDmode.DualShock4Controller); - LiveGlyph = "\uE000"; break; case HIDmode.DualShock4Controller: SettingsManager.SetProperty(SettingsName, (int)HIDmode.Xbox360Controller); - LiveGlyph = "\uE001"; break; default: break; } } - base.Update(); - base.Execute(IsKeyDown, IsKeyUp); + Update(); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/HC/MainWindowCommands.cs b/HandheldCompanion/Commands/Functions/HC/MainWindowCommands.cs index 835b94da8..3c693fd3b 100644 --- a/HandheldCompanion/Commands/Functions/HC/MainWindowCommands.cs +++ b/HandheldCompanion/Commands/Functions/HC/MainWindowCommands.cs @@ -18,14 +18,14 @@ public MainWindowCommands() private void StateChanged(object? sender, EventArgs e) { - base.Execute(OnKeyDown, OnKeyUp); + base.Execute(OnKeyDown, OnKeyUp, true); } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { MainWindow.GetCurrent().SwapWindowState(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override bool IsToggled => MainWindow.GetCurrent().WindowState != System.Windows.WindowState.Minimized; diff --git a/HandheldCompanion/Commands/Functions/HC/OverlayGamepadCommands.cs b/HandheldCompanion/Commands/Functions/HC/OverlayGamepadCommands.cs index a8b38b353..7a68577eb 100644 --- a/HandheldCompanion/Commands/Functions/HC/OverlayGamepadCommands.cs +++ b/HandheldCompanion/Commands/Functions/HC/OverlayGamepadCommands.cs @@ -18,14 +18,14 @@ public OverlayGamepadCommands() private void IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) { - base.Execute(OnKeyDown, OnKeyUp); + base.Execute(OnKeyDown, OnKeyUp, true); } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { MainWindow.overlayModel.ToggleVisibility(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override bool IsToggled => MainWindow.overlayModel.Visibility == System.Windows.Visibility.Visible; diff --git a/HandheldCompanion/Commands/Functions/HC/OverlayTrackpadCommands.cs b/HandheldCompanion/Commands/Functions/HC/OverlayTrackpadCommands.cs index 24d56f719..51a78e6d0 100644 --- a/HandheldCompanion/Commands/Functions/HC/OverlayTrackpadCommands.cs +++ b/HandheldCompanion/Commands/Functions/HC/OverlayTrackpadCommands.cs @@ -18,14 +18,14 @@ public OverlayTrackpadCommands() private void IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) { - base.Execute(OnKeyDown, OnKeyUp); + base.Execute(OnKeyDown, OnKeyUp, true); } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { MainWindow.overlayTrackpad.ToggleVisibility(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override bool IsToggled => MainWindow.overlayTrackpad.Visibility == System.Windows.Visibility.Visible; diff --git a/HandheldCompanion/Commands/Functions/HC/QuickOverlayCommands.cs b/HandheldCompanion/Commands/Functions/HC/QuickOverlayCommands.cs new file mode 100644 index 000000000..f40627514 --- /dev/null +++ b/HandheldCompanion/Commands/Functions/HC/QuickOverlayCommands.cs @@ -0,0 +1,78 @@ +using HandheldCompanion.Managers; +using System; + +namespace HandheldCompanion.Commands.Functions.HC +{ + [Serializable] + public class QuickOverlayCommands : FunctionCommands + { + private const string SettingsName = "OnScreenDisplayLevel"; + + private bool _IsToggled = false; + private int prevDisplaylevel = 0; + + public QuickOverlayCommands() + { + base.Name = Properties.Resources.Hotkey_OnScreenDisplayToggle; + base.Description = Properties.Resources.Hotkey_OnScreenDisplayToggleDesc; + base.Glyph = "\uE78B"; + base.OnKeyUp = true; + + prevDisplaylevel = SettingsManager.GetInt(Settings.OnScreenDisplayLevel); + + SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; + } + + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) + { + switch (name) + { + case SettingsName: + if (!temporary) + prevDisplaylevel = Convert.ToInt16(value); + break; + } + } + + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) + { + switch (_IsToggled) + { + case true: + SettingsManager.SetProperty(SettingsName, prevDisplaylevel, false, true); + break; + case false: + SettingsManager.SetProperty(SettingsName, 0, false, true); + break; + } + + // invert toggle + _IsToggled = !_IsToggled; + + base.Execute(IsKeyDown, IsKeyUp, false); + } + + public override bool IsToggled => !_IsToggled; + + public override object Clone() + { + QuickOverlayCommands commands = new() + { + commandType = this.commandType, + Name = this.Name, + Description = this.Description, + Glyph = this.Glyph, + OnKeyUp = this.OnKeyUp, + OnKeyDown = this.OnKeyDown + }; + + return commands; + } + + public override void Dispose() + { + SettingsManager.SettingValueChanged -= SettingsManager_SettingValueChanged; + base.Dispose(); + } + } +} diff --git a/HandheldCompanion/Commands/Functions/HC/QuickToolsCommands.cs b/HandheldCompanion/Commands/Functions/HC/QuickToolsCommands.cs index 9395c15ac..1e27ece53 100644 --- a/HandheldCompanion/Commands/Functions/HC/QuickToolsCommands.cs +++ b/HandheldCompanion/Commands/Functions/HC/QuickToolsCommands.cs @@ -18,14 +18,14 @@ public QuickToolsCommands() private void IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e) { - base.Execute(OnKeyDown, OnKeyUp); + base.Execute(OnKeyDown, OnKeyUp, true); } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { OverlayQuickTools.GetCurrent().ToggleVisibility(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override bool IsToggled => OverlayQuickTools.GetCurrent().Visibility == System.Windows.Visibility.Visible; diff --git a/HandheldCompanion/Commands/Functions/Multimedia/BrightnessDecrease.cs b/HandheldCompanion/Commands/Functions/Multimedia/BrightnessDecrease.cs index e9b8598ae..29a9ceab9 100644 --- a/HandheldCompanion/Commands/Functions/Multimedia/BrightnessDecrease.cs +++ b/HandheldCompanion/Commands/Functions/Multimedia/BrightnessDecrease.cs @@ -14,11 +14,11 @@ public BrightnessDecrease() OnKeyDown = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { MultimediaManager.DecreaseBrightness(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Multimedia/BrightnessIncrease.cs b/HandheldCompanion/Commands/Functions/Multimedia/BrightnessIncrease.cs index 100112f50..124dd144f 100644 --- a/HandheldCompanion/Commands/Functions/Multimedia/BrightnessIncrease.cs +++ b/HandheldCompanion/Commands/Functions/Multimedia/BrightnessIncrease.cs @@ -14,11 +14,11 @@ public BrightnessIncrease() OnKeyDown = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { MultimediaManager.IncreaseBrightness(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Multimedia/VolumeDecrease.cs b/HandheldCompanion/Commands/Functions/Multimedia/VolumeDecrease.cs index f5468fceb..596431067 100644 --- a/HandheldCompanion/Commands/Functions/Multimedia/VolumeDecrease.cs +++ b/HandheldCompanion/Commands/Functions/Multimedia/VolumeDecrease.cs @@ -14,11 +14,11 @@ public VolumeDecrease() OnKeyDown = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { MultimediaManager.DecreaseVolume(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Multimedia/VolumeIncrease.cs b/HandheldCompanion/Commands/Functions/Multimedia/VolumeIncrease.cs index ddcf94358..b9e1bdbb2 100644 --- a/HandheldCompanion/Commands/Functions/Multimedia/VolumeIncrease.cs +++ b/HandheldCompanion/Commands/Functions/Multimedia/VolumeIncrease.cs @@ -14,11 +14,11 @@ public VolumeIncrease() OnKeyDown = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { MultimediaManager.IncreaseVolume(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Multimedia/VolumeMute.cs b/HandheldCompanion/Commands/Functions/Multimedia/VolumeMute.cs new file mode 100644 index 000000000..e3e7b121b --- /dev/null +++ b/HandheldCompanion/Commands/Functions/Multimedia/VolumeMute.cs @@ -0,0 +1,73 @@ +using HandheldCompanion.Managers; +using System; + +namespace HandheldCompanion.Commands.Functions.Multimedia +{ + [Serializable] + public class VolumeMute : FunctionCommands + { + public VolumeMute() + { + Name = Properties.Resources.Hotkey_muteVolume; + Description = Properties.Resources.Hotkey_muteVolumeDesc; + Glyph = "\uE74F"; + OnKeyDown = true; + + Update(); + + MultimediaManager.VolumeNotification += MultimediaManager_VolumeNotification; + } + + private void MultimediaManager_VolumeNotification(float volume) + { + Update(); + } + + public override void Update() + { + switch (IsToggled) + { + case true: + LiveGlyph = "\uE74F"; + break; + case false: + LiveGlyph = "\uE767"; + break; + } + + base.Update(); + } + + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) + { + MultimediaManager.ToggleMute(); + + Update(); + base.Execute(IsKeyDown, IsKeyUp, false); + } + + public override bool IsToggled => MultimediaManager.IsMuted(); + + public override object Clone() + { + VolumeMute commands = new() + { + commandType = commandType, + Name = Name, + Description = Description, + Glyph = Glyph, + LiveGlyph = LiveGlyph, + OnKeyUp = OnKeyUp, + OnKeyDown = OnKeyDown + }; + + return commands; + } + + public override void Dispose() + { + MultimediaManager.VolumeNotification -= MultimediaManager_VolumeNotification; + base.Dispose(); + } + } +} diff --git a/HandheldCompanion/Commands/Functions/Windows/KillForegroundCommands.cs b/HandheldCompanion/Commands/Functions/Multitasking/KillForegroundCommands.cs similarity index 77% rename from HandheldCompanion/Commands/Functions/Windows/KillForegroundCommands.cs rename to HandheldCompanion/Commands/Functions/Multitasking/KillForegroundCommands.cs index 757376045..2d0934dd3 100644 --- a/HandheldCompanion/Commands/Functions/Windows/KillForegroundCommands.cs +++ b/HandheldCompanion/Commands/Functions/Multitasking/KillForegroundCommands.cs @@ -2,7 +2,7 @@ using HandheldCompanion.Managers; using System; -namespace HandheldCompanion.Commands.Functions.Windows +namespace HandheldCompanion.Commands.Functions.Multitasking { [Serializable] public class KillForegroundCommands : FunctionCommands @@ -15,19 +15,18 @@ public KillForegroundCommands() OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { - // get current foreground process - ProcessEx fProcess = ProcessManager.GetForegroundProcess(); - - // kill if is alive try { + // get current foreground process + ProcessEx fProcess = ProcessManager.GetForegroundProcess(); + // kill if is alive fProcess?.Process?.Kill(); } catch { } - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Multitasking/SwapScreenCommands.cs b/HandheldCompanion/Commands/Functions/Multitasking/SwapScreenCommands.cs new file mode 100644 index 000000000..c5cc2d03d --- /dev/null +++ b/HandheldCompanion/Commands/Functions/Multitasking/SwapScreenCommands.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using WpfScreenHelper.Enum; + +namespace HandheldCompanion.Commands.Functions.Multitasking +{ + [Serializable] + public class SwapScreenCommands : FunctionCommands + { + private bool HasTwoScreen => Screen.AllScreens.Length > 1; + + public SwapScreenCommands() + { + Name = Properties.Resources.Hotkey_SwapScreen; + Description = Properties.Resources.Hotkey_SwapScreenDesc; + Glyph = "\ue8a7"; + OnKeyUp = true; + } + + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) + { + if (HasTwoScreen) + { + // get foreground window + IntPtr hWnd = WinAPI.GetforegroundWindow(); + + // get the other screen + Screen currentScreen = Screen.FromHandle(hWnd); + Screen nextScreen = Screen.AllScreens.Where(screen => screen.DeviceName != currentScreen.DeviceName).FirstOrDefault(); + if (nextScreen is not null) + { + // move window + WinAPI.MoveWindow(hWnd, nextScreen, WindowPositions.Maximize); + WinAPI.SetForegroundWindow(hWnd); + } + } + + base.Execute(IsKeyDown, IsKeyUp, false); + } + + public override object Clone() + { + SwapScreenCommands commands = new() + { + commandType = commandType, + Name = Name, + Description = Description, + Glyph = Glyph, + OnKeyUp = OnKeyUp, + OnKeyDown = OnKeyDown + }; + + return commands; + } + } +} diff --git a/HandheldCompanion/Commands/Functions/Multitasking/TaskManagerCommands.cs b/HandheldCompanion/Commands/Functions/Multitasking/TaskManagerCommands.cs new file mode 100644 index 000000000..464334579 --- /dev/null +++ b/HandheldCompanion/Commands/Functions/Multitasking/TaskManagerCommands.cs @@ -0,0 +1,40 @@ +using GregsStack.InputSimulatorStandard.Native; +using HandheldCompanion.Simulators; +using System; + +namespace HandheldCompanion.Commands.Functions.Multitasking +{ + [Serializable] + public class TaskManagerCommands : FunctionCommands + { + public TaskManagerCommands() + { + Name = Properties.Resources.Hotkey_TaskManager; + Description = Properties.Resources.Hotkey_TaskManagerDesc; + Glyph = "\uE9D9"; + OnKeyUp = true; + } + + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) + { + KeyboardSimulator.KeyPress(new[] { VirtualKeyCode.LCONTROL, VirtualKeyCode.LSHIFT, VirtualKeyCode.ESCAPE }); + + base.Execute(IsKeyDown, IsKeyUp, false); + } + + public override object Clone() + { + TaskManagerCommands commands = new() + { + commandType = commandType, + Name = Name, + Description = Description, + Glyph = Glyph, + OnKeyUp = OnKeyUp, + OnKeyDown = OnKeyDown + }; + + return commands; + } + } +} diff --git a/HandheldCompanion/Commands/Functions/Performance/TDPDecrease.cs b/HandheldCompanion/Commands/Functions/Performance/TDPDecrease.cs new file mode 100644 index 000000000..6fcb96ac1 --- /dev/null +++ b/HandheldCompanion/Commands/Functions/Performance/TDPDecrease.cs @@ -0,0 +1,51 @@ +using HandheldCompanion.Managers; +using HandheldCompanion.Misc; +using HandheldCompanion.Processors; +using System; + +namespace HandheldCompanion.Commands.Functions.Performance +{ + [Serializable] + public class TDPDecrease : FunctionCommands + { + public TDPDecrease() + { + Name = Properties.Resources.Hotkey_decreaseTDP; + Description = Properties.Resources.Hotkey_decreaseTDPDesc; + FontFamily = "Segoe UI Symbol"; + Glyph = "\u2796"; + OnKeyDown = true; + } + + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) + { + PowerProfile powerProfile = PowerProfileManager.GetCurrent(); + if (powerProfile.TDPOverrideEnabled && !powerProfile.DeviceDefault) + { + double TPDMin = PerformanceManager.GetMinimumTDP(); + for (int idx = (int)PowerType.Slow; idx <= (int)PowerType.Fast; idx++) + powerProfile.TDPOverrideValues[idx] = Math.Max(TPDMin, powerProfile.TDPOverrideValues[idx] - 1); + + PowerProfileManager.UpdateOrCreateProfile(powerProfile, UpdateSource.Background); + } + + base.Execute(IsKeyDown, IsKeyUp, false); + } + + public override object Clone() + { + TDPDecrease commands = new() + { + commandType = commandType, + Name = Name, + Description = Description, + FontFamily = FontFamily, + Glyph = Glyph, + OnKeyUp = OnKeyUp, + OnKeyDown = OnKeyDown + }; + + return commands; + } + } +} diff --git a/HandheldCompanion/Commands/Functions/Performance/TDPIncrease.cs b/HandheldCompanion/Commands/Functions/Performance/TDPIncrease.cs new file mode 100644 index 000000000..a1a0c5cae --- /dev/null +++ b/HandheldCompanion/Commands/Functions/Performance/TDPIncrease.cs @@ -0,0 +1,51 @@ +using HandheldCompanion.Managers; +using HandheldCompanion.Misc; +using HandheldCompanion.Processors; +using System; + +namespace HandheldCompanion.Commands.Functions.Performance +{ + [Serializable] + public class TDPIncrease : FunctionCommands + { + public TDPIncrease() + { + Name = Properties.Resources.Hotkey_increaseTDP; + Description = Properties.Resources.Hotkey_increaseTDPDesc; + FontFamily = "Segoe UI Symbol"; + Glyph = "\u2795"; + OnKeyDown = true; + } + + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) + { + PowerProfile powerProfile = PowerProfileManager.GetCurrent(); + if (powerProfile.TDPOverrideEnabled && !powerProfile.DeviceDefault) + { + double TDPMax = PerformanceManager.GetMaximumTDP(); + for (int idx = (int)PowerType.Slow; idx <= (int)PowerType.Fast; idx++) + powerProfile.TDPOverrideValues[idx] = Math.Min(TDPMax, powerProfile.TDPOverrideValues[idx] + 1); + + PowerProfileManager.UpdateOrCreateProfile(powerProfile, UpdateSource.Background); + } + + base.Execute(IsKeyDown, IsKeyUp, false); + } + + public override object Clone() + { + TDPIncrease commands = new() + { + commandType = commandType, + Name = Name, + Description = Description, + FontFamily = FontFamily, + Glyph = Glyph, + OnKeyUp = OnKeyUp, + OnKeyDown = OnKeyDown + }; + + return commands; + } + } +} diff --git a/HandheldCompanion/Commands/Functions/Windows/ActionCenterCommands.cs b/HandheldCompanion/Commands/Functions/Windows/ActionCenterCommands.cs index 234af8d4d..d45e74774 100644 --- a/HandheldCompanion/Commands/Functions/Windows/ActionCenterCommands.cs +++ b/HandheldCompanion/Commands/Functions/Windows/ActionCenterCommands.cs @@ -15,14 +15,14 @@ public ActionCenterCommands() OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { Task.Run(() => { Process.Start(new ProcessStartInfo("ms-actioncenter://") { UseShellExecute = true }); }); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Windows/GameBarCommands.cs b/HandheldCompanion/Commands/Functions/Windows/GameBarCommands.cs new file mode 100644 index 000000000..7b477d8ad --- /dev/null +++ b/HandheldCompanion/Commands/Functions/Windows/GameBarCommands.cs @@ -0,0 +1,40 @@ +using GregsStack.InputSimulatorStandard.Native; +using HandheldCompanion.Simulators; +using System; + +namespace HandheldCompanion.Commands.Functions.Windows +{ + [Serializable] + public class GameBarCommands : FunctionCommands + { + public GameBarCommands() + { + Name = Properties.Resources.Hotkey_GameBar; + Description = Properties.Resources.Hotkey_GameBarDesc; + Glyph = "\uE713"; + OnKeyUp = true; + } + + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) + { + KeyboardSimulator.KeyPress(new[] { VirtualKeyCode.LWIN, VirtualKeyCode.VK_G }); + + base.Execute(IsKeyDown, IsKeyUp, false); + } + + public override object Clone() + { + GameBarCommands commands = new() + { + commandType = commandType, + Name = Name, + Description = Description, + Glyph = Glyph, + OnKeyUp = OnKeyUp, + OnKeyDown = OnKeyDown + }; + + return commands; + } + } +} diff --git a/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardCommands.cs b/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardCommands.cs index 9931a1894..700b6ede0 100644 --- a/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardCommands.cs +++ b/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardCommands.cs @@ -14,11 +14,11 @@ public OnScreenKeyboardCommands() OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { OnScreenKeyboard.ToggleVisibility(); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardLegacyCommands.cs b/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardLegacyCommands.cs index a6191e651..a838284c0 100644 --- a/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardLegacyCommands.cs +++ b/HandheldCompanion/Commands/Functions/Windows/OnScreenKeyboardLegacyCommands.cs @@ -25,7 +25,7 @@ public OnScreenKeyboardLegacyCommands() OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { Task.Run(async () => { @@ -60,7 +60,7 @@ public override void Execute(bool IsKeyDown, bool IsKeyUp) } }); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Windows/ScreenshotCommands.cs b/HandheldCompanion/Commands/Functions/Windows/ScreenshotCommands.cs index 187ace054..3fb64680a 100644 --- a/HandheldCompanion/Commands/Functions/Windows/ScreenshotCommands.cs +++ b/HandheldCompanion/Commands/Functions/Windows/ScreenshotCommands.cs @@ -15,11 +15,11 @@ public ScreenshotCommands() OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { KeyboardSimulator.KeyPress(new[] { VirtualKeyCode.LWIN, VirtualKeyCode.LSHIFT, VirtualKeyCode.VK_S }); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/Functions/Windows/SettingsCommands.cs b/HandheldCompanion/Commands/Functions/Windows/SettingsCommands.cs index 41965021a..e99714cdf 100644 --- a/HandheldCompanion/Commands/Functions/Windows/SettingsCommands.cs +++ b/HandheldCompanion/Commands/Functions/Windows/SettingsCommands.cs @@ -15,14 +15,14 @@ public SettingsCommands() OnKeyUp = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { Task.Run(() => { Process.Start(new ProcessStartInfo("ms-settings://") { UseShellExecute = true }); }); - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Commands/ICommands.cs b/HandheldCompanion/Commands/ICommands.cs index 39fb5df31..151473afb 100644 --- a/HandheldCompanion/Commands/ICommands.cs +++ b/HandheldCompanion/Commands/ICommands.cs @@ -56,7 +56,7 @@ public string LiveGlyph public ICommands() { } - public virtual void Execute(bool IsKeyDown, bool IsKeyUp) + public virtual void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { Executed?.Invoke(this); } diff --git a/HandheldCompanion/Commands/KeyboardCommands.cs b/HandheldCompanion/Commands/KeyboardCommands.cs index 22f3a1f41..6fd863fbf 100644 --- a/HandheldCompanion/Commands/KeyboardCommands.cs +++ b/HandheldCompanion/Commands/KeyboardCommands.cs @@ -22,7 +22,7 @@ public KeyboardCommands() base.OnKeyDown = true; } - public override void Execute(bool IsKeyDown, bool IsKeyUp) + public override void Execute(bool IsKeyDown, bool IsKeyUp, bool IsBackground) { if (OnKeyDown && OnKeyUp) { @@ -59,7 +59,7 @@ public override void Execute(bool IsKeyDown, bool IsKeyUp) } } - base.Execute(IsKeyDown, IsKeyUp); + base.Execute(IsKeyDown, IsKeyUp, false); } public override object Clone() diff --git a/HandheldCompanion/Controllers/DInputController.cs b/HandheldCompanion/Controllers/DInputController.cs index 0c83ce3c7..99cea2bdc 100644 --- a/HandheldCompanion/Controllers/DInputController.cs +++ b/HandheldCompanion/Controllers/DInputController.cs @@ -34,10 +34,10 @@ public DInputController(Joystick joystick, PnPDetails details) public override string ToString() { - var baseName = base.ToString(); + string baseName = base.ToString(); if (!string.IsNullOrEmpty(baseName)) return baseName; - if (!string.IsNullOrEmpty(joystick.Information.ProductName)) + if (!string.IsNullOrEmpty(joystick?.Information.ProductName)) return joystick.Information.ProductName; return $"DInput Controller {UserIndex}"; } diff --git a/HandheldCompanion/Controllers/GordonController.cs b/HandheldCompanion/Controllers/GordonController.cs index a22a9d4d3..f17d93264 100644 --- a/HandheldCompanion/Controllers/GordonController.cs +++ b/HandheldCompanion/Controllers/GordonController.cs @@ -1,4 +1,5 @@ using HandheldCompanion.Actions; +using HandheldCompanion.Helpers; using HandheldCompanion.Inputs; using HandheldCompanion.Managers; using HandheldCompanion.Utils; @@ -217,7 +218,8 @@ public override void UpdateInputs(long ticks, float delta) Inputs.GyroState.SetAccelerometer(aX, aY, aZ); // process motion - gamepadMotion.ProcessMotion(gX, gY, gZ, aX, aY, aZ, delta); + if (gamepadMotions.TryGetValue(gamepadIndex, out GamepadMotion gamepadMotion)) + gamepadMotion.ProcessMotion(gX, gY, gZ, aX, aY, aZ, delta); base.UpdateInputs(ticks, delta); } diff --git a/HandheldCompanion/Controllers/IController.xaml.cs b/HandheldCompanion/Controllers/IController.xaml.cs index f4683422c..506500983 100644 --- a/HandheldCompanion/Controllers/IController.xaml.cs +++ b/HandheldCompanion/Controllers/IController.xaml.cs @@ -80,7 +80,8 @@ public partial class IController : UserControl public ButtonState InjectedButtons = new(); public ControllerState Inputs = new(); - protected GamepadMotion gamepadMotion = new(string.Empty, CalibrationMode.Manual); + protected byte gamepadIndex = 0; + protected Dictionary gamepadMotions = new(); protected double VibrationStrength = 1.0d; private byte _UserIndex = 255; @@ -93,7 +94,9 @@ public partial class IController : UserControl protected object hidLock = new(); public virtual bool IsReady => true; - public virtual bool IsWireless => false; + public virtual bool IsWireless => Details.isBluetooth; + public virtual bool IsDongle => Details.isDongle; + public bool isPlaceholder; public bool IsBusy @@ -216,6 +219,8 @@ public IController() InitializeComponent(); InitializeInputOutput(); + gamepadMotions[gamepadIndex] = new(string.Empty, CalibrationMode.Manual); + MaxUserIndex = UserIndexPanel.Children.Count; } @@ -237,12 +242,12 @@ public virtual void AttachDetails(PnPDetails details) return; // manage gamepad motion - gamepadMotion = new(details.deviceInstanceId, CalibrationMode.Manual | CalibrationMode.SensorFusion); + gamepadMotions[gamepadIndex] = new(details.deviceInstanceId, CalibrationMode.Manual | CalibrationMode.SensorFusion); // UI thread Application.Current.Dispatcher.Invoke(() => { - ControllerType.Glyph = details.isInternal ? "\uE990" : details.isBluetooth ? "\uE702" : "\uECF0"; + ControllerType.Glyph = details.isInternal ? "\uE990" : IsWireless ? "\uE702" : IsDongle ? "\uECF1" : "\uECF0"; }); /* @@ -255,12 +260,7 @@ public virtual void AttachDetails(PnPDetails details) public virtual void UpdateInputs(long ticks, float delta) { - InputsUpdated?.Invoke(Inputs, gamepadMotion, delta); - } - - public virtual void UpdateInputs(long ticks, float delta, GamepadMotion gamepadOverwrite) - { - InputsUpdated?.Invoke(Inputs, gamepadOverwrite, delta); + InputsUpdated?.Invoke(Inputs, gamepadMotions, delta, gamepadIndex); } public bool HasMotionSensor() @@ -270,7 +270,7 @@ public bool HasMotionSensor() public GamepadMotion GetMotionSensor() { - return gamepadMotion; + return gamepadMotions[gamepadIndex]; } public bool IsPhysical() @@ -557,9 +557,9 @@ public virtual bool RestoreDrivers() return true; } - public async void Calibrate() + public virtual async void Calibrate() { - SensorsManager.Calibrate(gamepadMotion); + SensorsManager.Calibrate(gamepadMotions); } protected virtual void ui_button_calibrate_Click(object sender, RoutedEventArgs e) @@ -832,13 +832,11 @@ 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, GamepadMotion gamepadMotion, float delta); - + public delegate void InputsUpdatedEventHandler(ControllerState Inputs, Dictionary gamepadMotions, float delta, byte gamepadIndex); #endregion } } diff --git a/HandheldCompanion/Controllers/JSController.cs b/HandheldCompanion/Controllers/JSController.cs index ebc21dac3..564190800 100644 --- a/HandheldCompanion/Controllers/JSController.cs +++ b/HandheldCompanion/Controllers/JSController.cs @@ -1,4 +1,5 @@ -using HandheldCompanion.Inputs; +using HandheldCompanion.Helpers; +using HandheldCompanion.Inputs; using HandheldCompanion.Utils; using Nefarius.Utilities.DeviceManagement.PnP; using System; @@ -119,7 +120,8 @@ public virtual void UpdateState(float delta) Inputs.GyroState.SetAccelerometer(iMU_STATE.accelX, iMU_STATE.accelY, iMU_STATE.accelZ); // process motion - gamepadMotion.ProcessMotion(iMU_STATE.gyroX, iMU_STATE.gyroY, iMU_STATE.gyroZ, iMU_STATE.accelX, iMU_STATE.accelY, iMU_STATE.accelZ, delta); + if (gamepadMotions.TryGetValue(gamepadIndex, out GamepadMotion gamepadMotion)) + gamepadMotion.ProcessMotion(iMU_STATE.gyroX, iMU_STATE.gyroY, iMU_STATE.gyroZ, iMU_STATE.accelX, iMU_STATE.accelY, iMU_STATE.accelZ, delta); } } diff --git a/HandheldCompanion/Controllers/LegionController.cs b/HandheldCompanion/Controllers/LegionController.cs index ded7a01e6..60f367166 100644 --- a/HandheldCompanion/Controllers/LegionController.cs +++ b/HandheldCompanion/Controllers/LegionController.cs @@ -9,8 +9,6 @@ using System.Numerics; using System.Runtime.InteropServices; using System.Threading; -using System.Windows; -using System.Windows.Forms; using static HandheldCompanion.Devices.Lenovo.SapientiaUsb; namespace HandheldCompanion.Controllers @@ -60,8 +58,26 @@ private enum ControllerState private Thread dataThread; private bool dataThreadRunning; - private byte[] Data = new byte[64]; + + #region TouchVariables + private bool IsPassthrough = false; + private bool touchpadTouched = false; + private DateTime touchStartTime; + private DateTime lastTapTime = DateTime.MinValue; + private Vector2 lastTapPosition; + private Vector2 touchStartPosition; + private const int doubleTapMaxDistance = 100; // Example threshold distance + private const int doubleTapMaxTime = 300; // Maximum time between taps in milliseconds + private uint longTapDuration = 500; // Threshold for a long tap in milliseconds + private const int longTapMaxMovement = 50; // Maximum movement allowed for a long tap + private bool longTapTriggered = false; + private bool validTapPosition = false; + private bool doubleTapPending = false; + private bool doubleTapped = false; + private Vector2 lastKnownPosition; + #endregion + public override bool IsReady { get @@ -81,22 +97,6 @@ public override bool IsWireless } } - private bool IsPassthrough = false; - private int GyroIndex = LegionGo.RightJoyconIndex; - - 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 - 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 - - private GamepadMotion gamepadMotionR; public LegionController() : base() { } @@ -107,7 +107,7 @@ public LegionController(PnPDetails details) : base(details) Capabilities |= ControllerCapabilities.MotionSensor; // get long press time from system settings - SystemParametersInfo(0x006A, 0, ref LongPressTime, 0); + SystemParametersInfo(0x006A, 0, ref longTapDuration, 0); SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; UpdateSettings(); @@ -148,7 +148,7 @@ protected override void UpdateSettings() SetGyroIndex(SettingsManager.GetInt("LegionControllerGyroIndex")); } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { @@ -165,8 +165,8 @@ public override void AttachDetails(PnPDetails details) { base.AttachDetails(details); - // manage gamepad motion - gamepadMotionR = new($"{details.deviceInstanceId}\\{LegionGo.RightJoyconIndex}", CalibrationMode.Manual | CalibrationMode.SensorFusion); + // manage gamepad motion from right controller + gamepadMotions[1] = new($"{details.deviceInstanceId}\\{LegionGo.RightJoyconIndex}", CalibrationMode.Manual | CalibrationMode.SensorFusion); hidDevice = GetHidDevice(); if (hidDevice is not null) @@ -226,6 +226,8 @@ public override void Plug() public override void Unplug() { + SetPassthrough(true); + // Kill data thread if (dataThread is not null) { @@ -248,6 +250,9 @@ public override void Unplug() base.Unplug(); } + protected float aX = 0.0f, aZ = 0.0f, aY = 0.0f; + protected float gX = 0.0f, gZ = 0.0f, gY = 0.0f; + public override void UpdateInputs(long ticks, float delta, bool commit) { // skip if controller isn't connected @@ -270,88 +275,60 @@ public override void UpdateInputs(long ticks, float delta, bool commit) 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; - // 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); - */ + { + // Right Pad + ushort TouchpadX = (ushort)((Data[25] << 8) | Data[26]); + ushort TouchpadY = (ushort)((Data[27] << 8) | Data[28]); + bool touched = (TouchpadX != 0 || TouchpadY != 0); - float aX, aZ, aY = 0; - float gX, gZ, gY = 0; + HandleTouchpadInput(touched, TouchpadX, TouchpadY); + } - switch (GyroIndex) + for (byte idx = 0; idx <= 1; ++idx) { - default: - case LegionGo.LeftJoyconIndex: - { - aX = (short)(Data[34] << 8 | Data[35]) * -(4.0f / short.MaxValue); - aZ = (short)(Data[36] << 8 | Data[37]) * -(4.0f / short.MaxValue); - aY = (short)(Data[38] << 8 | Data[39]) * -(4.0f / short.MaxValue); - - gX = (short)(Data[40] << 8 | Data[41]) * -(2000.0f / short.MaxValue); - gZ = (short)(Data[42] << 8 | Data[43]) * -(2000.0f / short.MaxValue); - gY = (short)(Data[44] << 8 | Data[45]) * -(2000.0f / short.MaxValue); - } - break; + switch (idx) + { + default: + case 0: // LeftJoycon + { + aX = (short)(Data[34] << 8 | Data[35]) * -(4.0f / short.MaxValue); + aZ = (short)(Data[36] << 8 | Data[37]) * -(4.0f / short.MaxValue); + aY = (short)(Data[38] << 8 | Data[39]) * -(4.0f / short.MaxValue); - case LegionGo.RightJoyconIndex: - { - aX = (short)(Data[49] << 8 | Data[50]) * -(4.0f / short.MaxValue); - aZ = (short)(Data[47] << 8 | Data[48]) * (4.0f / short.MaxValue); - aY = (short)(Data[51] << 8 | Data[52]) * -(4.0f / short.MaxValue); + gX = (short)(Data[40] << 8 | Data[41]) * -(2000.0f / short.MaxValue); + gZ = (short)(Data[42] << 8 | Data[43]) * -(2000.0f / short.MaxValue); + gY = (short)(Data[44] << 8 | Data[45]) * -(2000.0f / short.MaxValue); + } + break; - gX = (short)(Data[55] << 8 | Data[56]) * -(2000.0f / short.MaxValue); - gZ = (short)(Data[53] << 8 | Data[54]) * (2000.0f / short.MaxValue); - gY = (short)(Data[57] << 8 | Data[58]) * -(2000.0f / short.MaxValue); - } - break; - } + case 1: // RightJoycon + { + aX = (short)(Data[49] << 8 | Data[50]) * -(4.0f / short.MaxValue); + aZ = (short)(Data[47] << 8 | Data[48]) * (4.0f / short.MaxValue); + aY = (short)(Data[51] << 8 | Data[52]) * -(4.0f / short.MaxValue); - // store motion - Inputs.GyroState.SetGyroscope(gX, gY, gZ); - Inputs.GyroState.SetAccelerometer(aX, aY, aZ); + gX = (short)(Data[55] << 8 | Data[56]) * -(2000.0f / short.MaxValue); + gZ = (short)(Data[53] << 8 | Data[54]) * (2000.0f / short.MaxValue); + gY = (short)(Data[57] << 8 | Data[58]) * -(2000.0f / short.MaxValue); + } + break; + } - // process motion - switch (GyroIndex) - { - default: - case LegionGo.LeftJoyconIndex: + // compute motion from controller + if (gamepadMotions.TryGetValue(idx, out GamepadMotion gamepadMotion)) gamepadMotion.ProcessMotion(gX, gY, gZ, aX, aY, aZ, delta); - base.UpdateInputs(ticks, delta); - break; - case LegionGo.RightJoyconIndex: - gamepadMotionR.ProcessMotion(gX, gY, gZ, aX, aY, aZ, delta); - base.UpdateInputs(ticks, delta, gamepadMotionR); - break; - } - } - protected override async void ui_button_calibrate_Click(object sender, RoutedEventArgs e) - { - switch (GyroIndex) - { - default: - case LegionGo.LeftJoyconIndex: - SensorsManager.Calibrate(gamepadMotion); - break; - case LegionGo.RightJoyconIndex: - SensorsManager.Calibrate(gamepadMotionR); - break; + // store motion from user selected gyro (left, right) + if (idx == gamepadIndex) + { + Inputs.GyroState.SetGyroscope(gX, gY, gZ); + Inputs.GyroState.SetAccelerometer(aX, aY, aZ); + } } + + base.UpdateInputs(ticks, delta); } private void dataThreadLoop(object? obj) @@ -382,117 +359,110 @@ public override string GetGlyph(ButtonFlags button) return base.GetGlyph(button); } - public void HandleTouchpadInput(bool touched, ushort x, ushort y) + public void HandleTouchpadInput(bool touched, ushort TouchpadX, ushort TouchpadY) { // Convert the ushort values to Vector2 - Vector2 position = new Vector2(x, y); + Vector2 position = new Vector2(TouchpadX, TouchpadY); + + if (touched) + { + lastKnownPosition = position; + } + + Inputs.ButtonState[ButtonFlags.RightPadTouch] = touched; // 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); + Inputs.AxisState[AxisFlags.RightPadX] = (short)InputUtils.MapRange((short)TouchpadX, 0, 1000, short.MinValue, short.MaxValue); + Inputs.AxisState[AxisFlags.RightPadY] = (short)InputUtils.MapRange((short)-TouchpadY, 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) + touchStartTime = DateTime.Now; + touchStartPosition = position; + longTapTriggered = false; + validTapPosition = true; + + // Trigger double tap if pending + if (doubleTapPending && (DateTime.Now - lastTapTime).TotalMilliseconds <= doubleTapMaxTime && + Vector2.Distance(lastTapPosition, lastKnownPosition) <= doubleTapMaxDistance) { - Inputs.ButtonState[ButtonFlags.RightPadClick] = true; - touchpadDoubleTapped = true; + HandleDoubleTap(lastKnownPosition); + doubleTapPending = false; + doubleTapped = true; + lastTapTime = DateTime.MinValue; // Reset lastTapTime after double tap } } - // If the touchpad was touched before - else + else if (!longTapTriggered && !doubleTapped && (DateTime.Now - touchStartTime).TotalMilliseconds >= longTapDuration) { - // Update the touchpad position - touchpadPosition = position; - - // If the touchpad has been double tapped - if (touchpadDoubleTapped) + // Check if the touch moved too much for a long tap + if (Vector2.Distance(touchStartPosition, position) <= longTapMaxMovement) { - // 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; - } - } + // Trigger long tap while the touch is held + HandleLongTap(position); + longTapTriggered = true; } } + else if (Vector2.Distance(touchStartPosition, position) > longTapMaxMovement) + { + validTapPosition = false; + } } // If the touchpad is not touched else { Inputs.AxisState[AxisFlags.RightPadX] = 0; Inputs.AxisState[AxisFlags.RightPadY] = 0; + Inputs.ButtonState[ButtonFlags.RightPadClick] = false; Inputs.ButtonState[ButtonFlags.RightPadClickDown] = false; // 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); + doubleTapped = false; + DateTime touchEndTime = DateTime.Now; + double touchDuration = (touchEndTime - touchStartTime).TotalMilliseconds; - // 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) + // Handle short tap + if (touchDuration < longTapDuration && !longTapTriggered && validTapPosition && + Vector2.Distance(touchStartPosition, lastKnownPosition) <= doubleTapMaxDistance) { - // Set the right pad click flag to true - Inputs.ButtonState[ButtonFlags.RightPadClick] = true; - - // Store tap time - lastTap = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - lastTapPosition = touchpadPosition; - } - else - { - Inputs.ButtonState[ButtonFlags.RightPadClick] = false; + // Single short tap detected + HandleShortTap(lastKnownPosition); + lastTapTime = touchEndTime; + lastTapPosition = lastKnownPosition; + doubleTapPending = true; } - - // Set the touchpad long tapped flag to false - touchpadLongTapped = false; - - // Set the touchpad double tapped flag to false - touchpadDoubleTapped = false; } } } + private void HandleShortTap(Vector2 position) + { + // Handle short tap action here + Inputs.ButtonState[ButtonFlags.RightPadTouch] = true; + Inputs.ButtonState[ButtonFlags.RightPadClick] = true; + } + + private void HandleDoubleTap(Vector2 position) + { + // Handle double tap action here + Inputs.ButtonState[ButtonFlags.RightPadTouch] = true; + Inputs.ButtonState[ButtonFlags.RightPadClick] = true; + } + + private void HandleLongTap(Vector2 position) + { + // Handle long tap action here + Inputs.ButtonState[ButtonFlags.RightPadTouch] = true; + Inputs.ButtonState[ButtonFlags.RightPadClick] = true; + Inputs.ButtonState[ButtonFlags.RightPadClickDown] = true; + } + public void SetPassthrough(bool enabled) { SetTouchPadStatus(enabled ? 1 : 0); @@ -501,7 +471,7 @@ public void SetPassthrough(bool enabled) public void SetGyroIndex(int idx) { - GyroIndex = idx + LegionGo.LeftJoyconIndex; + gamepadIndex = (byte)idx; } } } \ No newline at end of file diff --git a/HandheldCompanion/Controllers/NeptuneController.cs b/HandheldCompanion/Controllers/NeptuneController.cs index c58672cf9..32fca6f9a 100644 --- a/HandheldCompanion/Controllers/NeptuneController.cs +++ b/HandheldCompanion/Controllers/NeptuneController.cs @@ -1,4 +1,5 @@ using HandheldCompanion.Actions; +using HandheldCompanion.Helpers; using HandheldCompanion.Inputs; using HandheldCompanion.Managers; using SharpDX.XInput; @@ -245,7 +246,8 @@ public override void UpdateInputs(long ticks, float delta) Inputs.GyroState.SetAccelerometer(aX, aY, aZ); // process motion - gamepadMotion.ProcessMotion(gX, gY, gZ, aX, aY, aZ, delta); + if (gamepadMotions.TryGetValue(gamepadIndex, out GamepadMotion gamepadMotion)) + gamepadMotion.ProcessMotion(gX, gY, gZ, aX, aY, aZ, delta); base.UpdateInputs(ticks, delta); } diff --git a/HandheldCompanion/Controllers/TatantulaProController.cs b/HandheldCompanion/Controllers/TatantulaProController.cs new file mode 100644 index 000000000..5fb05e451 --- /dev/null +++ b/HandheldCompanion/Controllers/TatantulaProController.cs @@ -0,0 +1,326 @@ +using HandheldCompanion.Devices; +using HandheldCompanion.Helpers; +using HandheldCompanion.Inputs; +using HidLibrary; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Threading; + +namespace HandheldCompanion.Controllers +{ + public class TatantulaProController : XInputController + { + private HidDevice hidDevice; + + private Thread dataThread; + private bool dataThreadRunning; + private byte[] Data = new byte[64]; + + protected float aX = 0.0f, aZ = 0.0f, aY = 0.0f; + protected float gX = 0.0f, gZ = 0.0f, gY = 0.0f; + private const byte EXTRABUTTON0_IDX = 11; + private const byte EXTRABUTTON1_IDX = 12; + private const byte EXTRABUTTON2_IDX = 13; + + [Flags] + private enum Button0Enum + { + None = 0, + M = 4 + } + + [Flags] + private enum Button1Enum + { + None = 0, + M1 = 1, + M2 = 2, + T1 = 4, + T2 = 8, + T3 = 16, + C1 = 32, + C2 = 64, + C3 = 128 + } + + [Flags] + private enum Button2Enum + { + None = 0, + C4 = 1 + } + + [Flags] + private enum ButtonLayout + { + Xbox = 64, + Nintendo = 128, + } + + public TatantulaProController() : base() + { } + + public TatantulaProController(PnPDetails details) : base(details) + { + // Capabilities + Capabilities |= ControllerCapabilities.MotionSensor; + } + + public override string ToString() + { + return $"GameSir Tarantula Pro PC Controller"; + } + + protected override void InitializeInputOutput() + { + SourceButtons.Add(ButtonFlags.R4); + SourceButtons.Add(ButtonFlags.L4); + SourceButtons.Add(ButtonFlags.L5); + + SourceButtons.Add(ButtonFlags.B5); + SourceButtons.Add(ButtonFlags.B6); + SourceButtons.Add(ButtonFlags.B7); + SourceButtons.Add(ButtonFlags.B8); + SourceButtons.Add(ButtonFlags.B9); + SourceButtons.Add(ButtonFlags.B10); + SourceButtons.Add(ButtonFlags.B11); + + SourceAxis.Add(AxisLayoutFlags.Gyroscope); + } + + 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; + } + + // (test: 0x01, normal: 0x02) + private byte[] ControllerMode = new byte[33] { 0x7, 0x04, 0x0a, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + // brigthness mode + private byte[] LEDMode = new byte[33] { 0x7, 0x06, 0x07, 0x01, 0x64, 0x32, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + // H V S + private byte[] LEDColor = new byte[33] { 0x7, 0x10, 0x07, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + // button action button + private byte[] ButtonMode = new byte[33] { 0x7, 0x13, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + // T3 C1 C2 T1 T2 C3 C4 M1 M2 + private byte[] ExtraButtons = new byte[] { 0x28, 0x29, 0x2a, 0x26, 0x27, 0x2b, 0x2c, 0x24, 0x25 }; + + // (nintendo: 0x02, xbox: 0x01) + private byte[] ControllerLayout = new byte[] { 0x07, 0x07, 0x09, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + private ButtonLayout GetLayout() + { + ButtonLayout layout = (ButtonLayout)Data[EXTRABUTTON2_IDX]; + return layout.HasFlag(ButtonLayout.Xbox) ? ButtonLayout.Xbox : ButtonLayout.Nintendo; + } + + private void SetVerboseMode() + { + // unlock raw hid report + ControllerMode[4] = 0x02; + hidDevice.Write(ControllerMode); + } + + private void SetTestMode() + { + // unlock raw hid report + ControllerMode[4] = 0x01; + hidDevice.Write(ControllerMode); + } + + public override void Plug() + { + hidDevice = GetHidDevice(); + if (hidDevice is not null && hidDevice.IsConnected) + { + if (!hidDevice.IsOpen) + hidDevice.OpenDevice(); + + // unlock raw hid report + SetTestMode(); + + foreach (byte button in ExtraButtons) + { + ButtonMode[10] = button; + hidDevice.Write(ButtonMode); + } + + /* + ControllerLayout[6] = 0x02; // Nintendo + hidDevice.Write(ControllerLayout); + + ButtonLayout layout = GetLayout(); + + ControllerLayout[6] = 0x01; // XBOX + hidDevice.Write(ControllerLayout); + + layout = GetLayout(); + */ + + /* + // LED ON + LEDMode[6] = 1; + hidDevice.Write(LEDMode); + */ + + // start data thread + if (dataThread is null) + { + dataThreadRunning = true; + dataThread = new Thread(dataThreadLoop) + { + IsBackground = true, + Priority = ThreadPriority.Highest + }; + dataThread.Start(); + } + } + + base.Plug(); + } + + public override void Unplug() + { + // Kill data thread + if (dataThread is not null) + { + dataThreadRunning = false; + // Ensure the thread has finished execution + if (dataThread.IsAlive) + dataThread.Join(); + dataThread = null; + } + + if (hidDevice is not null) + { + if (hidDevice.IsConnected && hidDevice.IsOpen) + hidDevice.CloseDevice(); + + hidDevice.Dispose(); + hidDevice = null; + } + + base.Unplug(); + } + + public void ColorToHSV(Color color, out double hue, out double saturation, out double value) + { + // Convert RGB values to a scale of 0 to 1 + float rScaled = color.R / 255f; + float gScaled = color.G / 255f; + float bScaled = color.B / 255f; + + // Find the maximum and minimum values of R, G and B + float max = Math.Max(rScaled, Math.Max(gScaled, bScaled)); + float min = Math.Min(rScaled, Math.Min(gScaled, bScaled)); + float delta = max - min; + + // Calculate V (value/brightness) - scaled to 100% + value = max * 100; + + // Calculate S (saturation) - scaled to 100% + saturation = (max == 0) ? 0 : (delta / max) * 100; + + // Calculate H (hue) + hue = color.GetHue(); + hue = hue / 360.0f * 255f; + } + + public override void SetLightColor(byte R, byte G, byte B) + { + if (hidDevice is null || !hidDevice.IsConnected || !hidDevice.IsOpen) + return; + + Color color = Color.FromArgb(R, G, B); + ColorToHSV(color, out double hue, out double saturation, out double value); + + LEDColor[5] = (byte)hue; + LEDColor[6] = (byte)saturation; + LEDColor[7] = (byte)value; + + hidDevice.Write(LEDColor); + } + + public override void UpdateInputs(long ticks, float delta, bool commit) + { + // skip if controller isn't connected + if (!IsConnected()) + return; + + base.UpdateInputs(ticks, delta, false); + + Button0Enum extraButtons0 = (Button0Enum)Data[EXTRABUTTON0_IDX]; + Inputs.ButtonState[ButtonFlags.L5] = extraButtons0.HasFlag(Button0Enum.M); + + Button1Enum extraButtons1 = (Button1Enum)Data[EXTRABUTTON1_IDX]; + Inputs.ButtonState[ButtonFlags.L4] = extraButtons1.HasFlag(Button1Enum.M1); + Inputs.ButtonState[ButtonFlags.R4] = extraButtons1.HasFlag(Button1Enum.M2); + + Button2Enum extraButtons2 = (Button2Enum)Data[EXTRABUTTON2_IDX]; + Inputs.ButtonState[ButtonFlags.B5] = extraButtons1.HasFlag(Button1Enum.C1); + Inputs.ButtonState[ButtonFlags.B6] = extraButtons1.HasFlag(Button1Enum.C2); + Inputs.ButtonState[ButtonFlags.B7] = extraButtons1.HasFlag(Button1Enum.C3); + Inputs.ButtonState[ButtonFlags.B8] = extraButtons2.HasFlag(Button2Enum.C4); + + Inputs.ButtonState[ButtonFlags.B9] = extraButtons1.HasFlag(Button1Enum.T1); + Inputs.ButtonState[ButtonFlags.B10] = extraButtons1.HasFlag(Button1Enum.T2); + Inputs.ButtonState[ButtonFlags.B11] = extraButtons1.HasFlag(Button1Enum.T3); + + aX = (short)(Data[14] << 8 | Data[15]) * (4.0f / short.MaxValue); + aZ = (short)(Data[16] << 8 | Data[17]) * -(4.0f / short.MaxValue); + aY = (short)(Data[18] << 8 | Data[19]) * (4.0f / short.MaxValue); + + gX = (short)(Data[20] << 8 | Data[21]) * (2000.0f / short.MaxValue); + gZ = (short)(Data[22] << 8 | Data[23]) * -(2000.0f / short.MaxValue); + gY = (short)(Data[24] << 8 | Data[25]) * (2000.0f / short.MaxValue); + + // compute motion from controller + if (gamepadMotions.TryGetValue(gamepadIndex, out GamepadMotion gamepadMotion)) + gamepadMotion.ProcessMotion(gX, gY, gZ, aX, aY, aZ, delta); + + Inputs.GyroState.SetGyroscope(gX, gY, gZ); + Inputs.GyroState.SetAccelerometer(aX, aY, aZ); + + base.UpdateInputs(ticks, delta); + } + + private void dataThreadLoop(object? obj) + { + // pull latest Data + while (dataThreadRunning) + { + HidDeviceData report = hidDevice?.ReadData(0); + if (report is not null) + Buffer.BlockCopy(report.Data, 1, Data, 0, report.Data.Length - 1); + } + } + + public override string GetGlyph(ButtonFlags button) + { + return base.GetGlyph(button); + } + } +} \ No newline at end of file diff --git a/HandheldCompanion/DSU/ClientRequestTimes.cs b/HandheldCompanion/DSU/ClientRequestTimes.cs new file mode 100644 index 000000000..192a440d6 --- /dev/null +++ b/HandheldCompanion/DSU/ClientRequestTimes.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Net.NetworkInformation; + +namespace HandheldCompanion.DSU +{ + public 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() + { + allPads = DateTime.MinValue; + padIds = new DateTime[4]; + + for (int i = 0; i < padIds.Length; i++) + padIds[i] = DateTime.MinValue; + + padMacs = []; + } + + public void RequestPadInfo(byte regFlags, byte idToReg, PhysicalAddress macToReg) + { + if (regFlags == 0) + allPads = DateTime.UtcNow; + else + { + if ((regFlags & 0x01) != 0) //id valid + { + if (idToReg < padIds.Length) + padIds[idToReg] = DateTime.UtcNow; + } + if ((regFlags & 0x02) != 0) //mac valid + { + padMacs[macToReg] = DateTime.UtcNow; + } + } + } + } +} diff --git a/HandheldCompanion/DSU/DSUServer.cs b/HandheldCompanion/DSU/DSUServer.cs index 597b2435e..4b40713b2 100644 --- a/HandheldCompanion/DSU/DSUServer.cs +++ b/HandheldCompanion/DSU/DSUServer.cs @@ -1,5 +1,7 @@ using Force.Crc32; using HandheldCompanion.Controllers; +using HandheldCompanion.DSU; +using HandheldCompanion.Helpers; using HandheldCompanion.Inputs; using HandheldCompanion.Managers; using HandheldCompanion.Utils; @@ -9,8 +11,10 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; +using System.Numerics; using System.Threading; using System.Windows.Forms; +using static HandheldCompanion.Inputs.GyroState; namespace HandheldCompanion; @@ -48,56 +52,37 @@ public enum DsBattery : byte Charged = 0xEF } -public struct DualShockPadMeta +public static class DSUServer { - public byte PadId; - public DsState PadState; - public DsConnection ConnectionType; - public DsModel Model; - public PhysicalAddress PadMacAddress; - public DsBattery BatteryStatus; - public bool IsActive; -} - -public class DSUServer -{ - public delegate void GetPadDetail(int padIdx, ref DualShockPadMeta meta); - - public delegate void StartedEventHandler(DSUServer server); - - public delegate void StoppedEventHandler(DSUServer server); - - public const int NUMBER_SLOTS = 4; + private const byte NUMBER_SLOTS = 4; private const int ARG_BUFFER_LEN = 80; - private const ushort MaxProtocolVersion = 1001; - private SemaphoreSlim _pool; - //private SocketAsyncEventArgs[] argsList; - private byte[][] dataBuffers; - - private readonly Dictionary clients = []; - - private ControllerState Inputs = new(); - private int listInd; - private readonly PhysicalAddress PadMacAddress; - - public DualShockPadMeta padMeta; - private readonly ReaderWriterLockSlim poolLock = new(); - - public int port = 26760; - - private readonly GetPadDetail portInfoGet; - private readonly byte[] recvBuffer = new byte[1024]; - public bool running; - private uint serverId; - private int udpPacketCount; - private Socket udpSock; - - public DSUServer() - { - PadMacAddress = new PhysicalAddress(new byte[] { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 }); - portInfoGet = GetPadDetailForIdx; + private static SemaphoreSlim _pool; + private static byte[][] dataBuffers; + + private static readonly Dictionary clients = []; + private static readonly DualShockPadMeta[] padMetas = new DualShockPadMeta[NUMBER_SLOTS]; + private static readonly ReaderWriterLockSlim poolLock = new(); + private static readonly byte[] recvBuffer = new byte[1024]; + + private const int serverPort = 26760; + private static uint serverId; + private static int udpPacketCount; + private static Socket udpSock; + + public static bool IsInitialized; + + #region events + public delegate void StartedEventHandler(); + public static event StartedEventHandler Started; + + public delegate void StoppedEventHandler(); + public static event StoppedEventHandler Stopped; + #endregion + + static DSUServer() + { _pool = new SemaphoreSlim(ARG_BUFFER_LEN); dataBuffers = new byte[ARG_BUFFER_LEN][]; for (int num = 0; num < ARG_BUFFER_LEN; num++) @@ -107,45 +92,41 @@ public DSUServer() dataBuffers[num] = new byte[100]; } - padMeta = new DualShockPadMeta() - { - BatteryStatus = DsBattery.Full, - ConnectionType = DsConnection.Usb, - IsActive = true, - PadId = 0, - PadMacAddress = PadMacAddress, - Model = DsModel.DS4, - PadState = DsState.Connected - }; - } - - private void GetPadDetailForIdx(int padIdx, ref DualShockPadMeta meta) - { - meta = padMeta; + for (byte padIdx = 0; padIdx < NUMBER_SLOTS; padIdx++) + { + byte address = (byte)(0x10 + padIdx); + + padMetas[padIdx] = new DualShockPadMeta() + { + BatteryStatus = DsBattery.Full, + ConnectionType = DsConnection.Usb, + IsActive = true, + PadId = padIdx, + PadMacAddress = new PhysicalAddress([address, address, address, address, address, address]), + Model = DsModel.DS4, + PadState = DsState.Connected + }; + } } - public event StartedEventHandler Started; - - public event StoppedEventHandler Stopped; - - public override string ToString() + private static void GetPadDetailForIdx(int padIdx, ref DualShockPadMeta meta) { - return GetType().Name; + meta = padMetas[padIdx]; } - private void SocketEvent_AsyncCompleted(object sender, SocketAsyncEventArgs e) + private static void SocketEvent_AsyncCompleted(object sender, SocketAsyncEventArgs e) { _pool.Release(); e.Dispose(); } - private void CompletedSynchronousSocketEvent(SocketAsyncEventArgs args) + private static void CompletedSynchronousSocketEvent(SocketAsyncEventArgs args) { _pool.Release(); args.Dispose(); } - private int BeginPacket(byte[] packetBuf, ushort reqProtocolVersion = MaxProtocolVersion) + private static int BeginPacket(byte[] packetBuf, ushort reqProtocolVersion = MaxProtocolVersion) { var currIdx = 0; packetBuf[currIdx++] = (byte)'D'; @@ -168,7 +149,7 @@ private int BeginPacket(byte[] packetBuf, ushort reqProtocolVersion = MaxProtoco return currIdx; } - private void FinishPacket(byte[] packetBuf) + private static void FinishPacket(byte[] packetBuf) { Array.Clear(packetBuf, 8, 4); @@ -176,7 +157,8 @@ private void FinishPacket(byte[] packetBuf) Array.Copy(BitConverter.GetBytes(crcCalc), 0, packetBuf, 8, 4); } - private void SendPacket(IPEndPoint clientEP, byte[] usefulData, ushort reqProtocolVersion = MaxProtocolVersion) + private static int listInd; + private static void SendPacket(IPEndPoint clientEP, byte[] usefulData, ushort reqProtocolVersion = MaxProtocolVersion) { byte[] packetData = new byte[usefulData.Length + 16]; int currIdx = BeginPacket(packetData, reqProtocolVersion); @@ -212,7 +194,7 @@ private void SendPacket(IPEndPoint clientEP, byte[] usefulData, ushort reqProtoc } } - private void ProcessIncoming(byte[] localMsg, IPEndPoint clientEP) + private static void ProcessIncoming(byte[] localMsg, IPEndPoint clientEP) { try { @@ -293,7 +275,7 @@ private void ProcessIncoming(byte[] localMsg, IPEndPoint clientEP) { var currRequest = localMsg[requestsIdx + i]; var padData = new DualShockPadMeta(); - portInfoGet(currRequest, ref padData); + GetPadDetailForIdx(currRequest, ref padData); var outIdx = 0; Array.Copy(BitConverter.GetBytes((uint)MessageType.DSUS_PortInfo), 0, outputData, outIdx, 4); @@ -359,7 +341,7 @@ private void ProcessIncoming(byte[] localMsg, IPEndPoint clientEP) } } - private void ReceiveCallback(IAsyncResult iar) + private static void ReceiveCallback(IAsyncResult iar) { byte[] localMsg = null; EndPoint clientEP = new IPEndPoint(IPAddress.Any, 0); @@ -375,7 +357,7 @@ private void ReceiveCallback(IAsyncResult iar) } catch (SocketException) { - if (running) + if (IsInitialized) { ResetUDPConn(); } @@ -390,11 +372,11 @@ private void ReceiveCallback(IAsyncResult iar) ProcessIncoming(localMsg, (IPEndPoint)clientEP); } - private void StartReceive() + private static void StartReceive() { try { - if (running) + if (IsInitialized) { //Start listening for a new message. EndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); @@ -403,7 +385,7 @@ private void StartReceive() } catch (SocketException /*ex*/) { - if (running) + if (IsInitialized) { ResetUDPConn(); StartReceive(); @@ -417,7 +399,7 @@ private void StartReceive() /// Frees Socket from potentially firing SocketException instances after a client /// connection is terminated. Avoids memory leak /// - private void ResetUDPConn() + private static void ResetUDPConn() { if (udpSock is null) return; @@ -433,18 +415,18 @@ private void ResetUDPConn() catch (ObjectDisposedException) { } } - public bool Start() + public static bool Start() { try { udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - udpSock.Bind(new IPEndPoint(IPAddress.Any, port)); + udpSock.Bind(new IPEndPoint(IPAddress.Any, serverPort)); } catch (SocketException) { - LogManager.LogCritical("{0} couldn't listen to port: {1}", ToString(), port); + LogManager.LogCritical("DSUServer couldn't listen to port: {0}", serverPort); Stop(); - return running; + return IsInitialized; } catch (Exception /*ex*/) { } @@ -454,17 +436,17 @@ public bool Start() TimerManager.Tick += Tick; - running = true; + IsInitialized = true; StartReceive(); - LogManager.LogInformation("{0} has started. Listening to port: {1}", ToString(), port); - Started?.Invoke(this); + LogManager.LogInformation("DSUServer has started. Listening to port: {0}", serverPort); + Started?.Invoke(); - return running; + return IsInitialized; } - public void Stop() + public static void Stop() { if (udpSock is not null) { @@ -476,20 +458,24 @@ public void Stop() udpSock = null; } - running = false; + IsInitialized = false; TimerManager.Tick -= Tick; - LogManager.LogInformation("{0} has stopped", ToString()); - Stopped?.Invoke(this); - } + LogManager.LogInformation("DSUServer has stopped"); + Stopped?.Invoke(); + } + + private static ControllerState Inputs = new(); + private static Dictionary GamepadMotions = new(); - public void UpdateInputs(ControllerState inputs) + public static void UpdateInputs(ControllerState inputs, Dictionary gamepadMotions) { Inputs = inputs; - } + GamepadMotions = gamepadMotions; + } - private bool ReportToBuffer(byte[] outputData, ref int outIdx) + private static bool ReportToBuffer(byte[] outputData, ref int outIdx, byte padIdx) { unchecked { @@ -518,11 +504,8 @@ private bool ReportToBuffer(byte[] outputData, ref int outIdx) if (Inputs.AxisState[AxisFlags.L2] == byte.MaxValue) outputData[outIdx] |= 0x01; outputData[++outIdx] = - Convert.ToByte(Inputs.ButtonState[ButtonFlags.Special]); // (hidReport.PS) ? (byte)1 : - outputData[++outIdx] = Convert.ToByte(Inputs.ButtonState[ButtonFlags.LeftPadClick] || - Inputs.ButtonState[ - ButtonFlags - .RightPadClick]); // (hidReport.TouchButton) ? (byte)1 : + Convert.ToByte(Inputs.ButtonState[ButtonFlags.Special]); // (hidReport.PS) ? (byte)1 : (byte)0 + outputData[++outIdx] = Convert.ToByte(Inputs.ButtonState[ButtonFlags.LeftPadClick] || Inputs.ButtonState[ButtonFlags.RightPadClick]); // (hidReport.TouchButton) ? (byte)1 : (byte)0 //Left stick outputData[++outIdx] = InputUtils.NormalizeXboxInput(Inputs.AxisState[AxisFlags.LeftStickX]); @@ -567,235 +550,225 @@ private bool ReportToBuffer(byte[] outputData, ref int outIdx) //motion timestamp Array.Copy(BitConverter.GetBytes((ulong)TimerManager.GetElapsedSeconds()), 0, outputData, outIdx, 8); - outIdx += 8; - - // Accelerometer - // accelXG - Array.Copy(BitConverter.GetBytes(Inputs.GyroState.Accelerometer[GyroState.SensorState.DSU].X), 0, outputData, outIdx, 4); - outIdx += 4; - // accelYG - Array.Copy(BitConverter.GetBytes(Inputs.GyroState.Accelerometer[GyroState.SensorState.DSU].Y), 0, outputData, outIdx, 4); - outIdx += 4; - // accelZG - Array.Copy(BitConverter.GetBytes(Inputs.GyroState.Accelerometer[GyroState.SensorState.DSU].Z), 0, outputData, outIdx, 4); - outIdx += 4; - - // Gyroscope - // angVelPitch - Array.Copy(BitConverter.GetBytes(Inputs.GyroState.Gyroscope[GyroState.SensorState.DSU].X), 0, outputData, outIdx, 4); - outIdx += 4; - // angVelYaw - Array.Copy(BitConverter.GetBytes(-Inputs.GyroState.Gyroscope[GyroState.SensorState.DSU].Y), 0, outputData, outIdx, 4); - outIdx += 4; - // angVelRoll - Array.Copy(BitConverter.GetBytes(-Inputs.GyroState.Gyroscope[GyroState.SensorState.DSU].Z), 0, outputData, outIdx, 4); + outIdx += 8; + + float gyroX = 0.0f, gyroY = 0.0f, gyroZ = 0.0f; + float accelX = 0.0f, accelY = 0.0f, accelZ = 0.0f; + switch (padIdx) + { + default: + { + if (Inputs.GyroState.Gyroscope.TryGetValue(SensorState.DSU, out Vector3 gyrometer)) + { + gyroX = gyrometer.X; + gyroY = gyrometer.Y; + gyroZ = gyrometer.Z; + } + + if (Inputs.GyroState.Accelerometer.TryGetValue(SensorState.DSU, out Vector3 accelerometer)) + { + accelX = accelerometer.X; + accelY = accelerometer.Y; + accelZ = accelerometer.Z; + } + } + break; + + case 1: + case 2: + byte gamepadMotionIdx = (byte)(padIdx - 1); + if (GamepadMotions.TryGetValue(gamepadMotionIdx, out GamepadMotion gamepadMotion)) + { + gamepadMotion.GetRawGyro(out gyroX, out gyroY, out gyroZ); + gamepadMotion.GetRawAcceleration(out accelX, out accelY, out accelZ); + } + break; + + case 3: + // do nothing + return true; + } + + accelX = accelX * -1.0f; + accelY = accelY * -1.0f; + accelZ = accelZ * -1.0f; + + // Accelerometer + Array.Copy(BitConverter.GetBytes(accelX), 0, outputData, outIdx, 4); + outIdx += 4; + Array.Copy(BitConverter.GetBytes(accelY), 0, outputData, outIdx, 4); + outIdx += 4; + Array.Copy(BitConverter.GetBytes(accelZ), 0, outputData, outIdx, 4); + outIdx += 4; + + // Gyroscope + Array.Copy(BitConverter.GetBytes(gyroX), 0, outputData, outIdx, 4); + outIdx += 4; + Array.Copy(BitConverter.GetBytes(-gyroY), 0, outputData, outIdx, 4); + outIdx += 4; + Array.Copy(BitConverter.GetBytes(-gyroZ), 0, outputData, outIdx, 4); outIdx += 4; } return true; } - public void Tick(long ticks, float delta) + public static void Tick(long ticks, float delta) { - if (!running) - return; - - // only update every one second - if (ticks % 1000 == 0) - { - var ChargeStatus = SystemInformation.PowerStatus.BatteryChargeStatus; - - if (ChargeStatus.HasFlag(BatteryChargeStatus.Charging)) - padMeta.BatteryStatus = DsBattery.Charging; - else if (ChargeStatus.HasFlag(BatteryChargeStatus.NoSystemBattery)) - padMeta.BatteryStatus = DsBattery.None; - else if (ChargeStatus.HasFlag(BatteryChargeStatus.High)) - padMeta.BatteryStatus = DsBattery.High; - else if (ChargeStatus.HasFlag(BatteryChargeStatus.Low)) - padMeta.BatteryStatus = DsBattery.Low; - else if (ChargeStatus.HasFlag(BatteryChargeStatus.Critical)) - padMeta.BatteryStatus = DsBattery.Dying; - else - padMeta.BatteryStatus = DsBattery.Medium; - } - - // update status - padMeta.IsActive = true; // fixme ? - - var clientsList = new List(); - var now = DateTime.UtcNow; - lock (clients) - { - var clientsToDelete = new List(); - - foreach (var cl in clients) - { - const double TimeoutLimit = 5; - - if ((now - cl.Value.AllPadsTime).TotalSeconds < TimeoutLimit) - { - clientsList.Add(cl.Key); - } - else if (padMeta.PadId < cl.Value.PadIdsTime.Length && - (now - cl.Value.PadIdsTime[padMeta.PadId]).TotalSeconds < TimeoutLimit) - { - clientsList.Add(cl.Key); - } - else if (cl.Value.PadMacsTime.TryGetValue(padMeta.PadMacAddress, out var padTime) && - (now - padTime).TotalSeconds < TimeoutLimit) - { - clientsList.Add(cl.Key); - } - else //check if this client is totally dead, and remove it if so - { - var clientOk = false; - foreach (var t in cl.Value.PadIdsTime) - { - var dur = (now - t).TotalSeconds; - if (dur < TimeoutLimit) - { - clientOk = true; - break; - } - } - - if (!clientOk) - { - foreach (var dict in cl.Value.PadMacsTime) - { - var dur = (now - dict.Value).TotalSeconds; - if (dur < TimeoutLimit) - { - clientOk = true; - break; - } - } - - if (!clientOk) - clientsToDelete.Add(cl.Key); - } - } - } - - foreach (var delCl in clientsToDelete) clients.Remove(delCl); - clientsToDelete.Clear(); - clientsToDelete = null; - } - - if (clientsList.Count <= 0) - return; - - unchecked + if (!IsInitialized) + return; + + // only update every one second + for (byte padIdx = 0; padIdx < NUMBER_SLOTS; padIdx++) { - var outputData = new byte[100]; - var outIdx = BeginPacket(outputData); - Array.Copy(BitConverter.GetBytes((uint)MessageType.DSUS_PadDataRsp), 0, outputData, outIdx, 4); - outIdx += 4; - - outputData[outIdx++] = padMeta.PadId; - outputData[outIdx++] = (byte)padMeta.PadState; - outputData[outIdx++] = (byte)padMeta.Model; - outputData[outIdx++] = (byte)padMeta.ConnectionType; - { - var padMac = padMeta.PadMacAddress.GetAddressBytes(); - outputData[outIdx++] = padMac[0]; - outputData[outIdx++] = padMac[1]; - outputData[outIdx++] = padMac[2]; - outputData[outIdx++] = padMac[3]; - outputData[outIdx++] = padMac[4]; - outputData[outIdx++] = padMac[5]; - } - outputData[outIdx++] = (byte)padMeta.BatteryStatus; - outputData[outIdx++] = padMeta.IsActive ? (byte)1 : (byte)0; - - Array.Copy(BitConverter.GetBytes((uint)udpPacketCount++), 0, outputData, outIdx, 4); - outIdx += 4; + DualShockPadMeta padMeta = padMetas[padIdx]; - if (!ReportToBuffer(outputData, ref outIdx)) - return; - FinishPacket(outputData); - - foreach (var cl in clientsList) - { - int temp = 0; - poolLock.EnterWriteLock(); - temp = listInd; - listInd = ++listInd % ARG_BUFFER_LEN; - SocketAsyncEventArgs args = new SocketAsyncEventArgs() + if (ticks % 1000 == 0) + { + BatteryChargeStatus ChargeStatus = SystemInformation.PowerStatus.BatteryChargeStatus; + + if (ChargeStatus.HasFlag(BatteryChargeStatus.Charging)) + padMeta.BatteryStatus = DsBattery.Charging; + else if (ChargeStatus.HasFlag(BatteryChargeStatus.NoSystemBattery)) + padMeta.BatteryStatus = DsBattery.None; + else if (ChargeStatus.HasFlag(BatteryChargeStatus.High)) + padMeta.BatteryStatus = DsBattery.High; + else if (ChargeStatus.HasFlag(BatteryChargeStatus.Low)) + padMeta.BatteryStatus = DsBattery.Low; + else if (ChargeStatus.HasFlag(BatteryChargeStatus.Critical)) + padMeta.BatteryStatus = DsBattery.Dying; + else + padMeta.BatteryStatus = DsBattery.Medium; + } + + // update status + padMeta.IsActive = true; // fixme ? + + List? clientsList = new List(); + DateTime now = DateTime.UtcNow; + lock (clients) + { + List? clientsToDelete = new List(); + + foreach (var cl in clients) { - RemoteEndPoint = cl, - }; - args.SetBuffer(dataBuffers[temp], 0, 100); - args.Completed += SocketEvent_AsyncCompleted; - poolLock.ExitWriteLock(); - - _pool.Wait(); - Array.Copy(outputData, args.Buffer, outputData.Length); - bool sentAsync = false; - try - { - bool sendAsync = udpSock.SendToAsync(args); - } - catch (SocketException /*ex*/) { } - catch (Exception /*ex*/) { } - finally - { - if (!sentAsync) CompletedSynchronousSocketEvent(args); - } - } - } - - clientsList.Clear(); - clientsList = null; - } - - private enum MessageType - { - DSUC_VersionReq = 0x100000, - DSUS_VersionRsp = 0x100000, - DSUC_ListPorts = 0x100001, - DSUS_PortInfo = 0x100001, - DSUC_PadDataReq = 0x100002, - DSUS_PadDataRsp = 0x100002 - } - - 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() - { - allPads = DateTime.MinValue; - padIds = new DateTime[4]; - - for (int i = 0; i < padIds.Length; i++) - padIds[i] = DateTime.MinValue; - - padMacs = []; - } - - public void RequestPadInfo(byte regFlags, byte idToReg, PhysicalAddress macToReg) - { - if (regFlags == 0) - allPads = DateTime.UtcNow; - else - { - if ((regFlags & 0x01) != 0) //id valid - { - if (idToReg < padIds.Length) - padIds[idToReg] = DateTime.UtcNow; - } - if ((regFlags & 0x02) != 0) //mac valid - { - padMacs[macToReg] = DateTime.UtcNow; - } - } + const double TimeoutLimit = 5; + + if ((now - cl.Value.AllPadsTime).TotalSeconds < TimeoutLimit) + { + clientsList.Add(cl.Key); + } + else if (padMeta.PadId < cl.Value.PadIdsTime.Length && + (now - cl.Value.PadIdsTime[padMeta.PadId]).TotalSeconds < TimeoutLimit) + { + clientsList.Add(cl.Key); + } + else if (cl.Value.PadMacsTime.TryGetValue(padMeta.PadMacAddress, out var padTime) && + (now - padTime).TotalSeconds < TimeoutLimit) + { + clientsList.Add(cl.Key); + } + else //check if this client is totally dead, and remove it if so + { + var clientOk = false; + foreach (var t in cl.Value.PadIdsTime) + { + var dur = (now - t).TotalSeconds; + if (dur < TimeoutLimit) + { + clientOk = true; + break; + } + } + + if (!clientOk) + { + foreach (var dict in cl.Value.PadMacsTime) + { + var dur = (now - dict.Value).TotalSeconds; + if (dur < TimeoutLimit) + { + clientOk = true; + break; + } + } + + if (!clientOk) + clientsToDelete.Add(cl.Key); + } + } + } + + foreach (var delCl in clientsToDelete) clients.Remove(delCl); + clientsToDelete.Clear(); + clientsToDelete = null; + } + + if (clientsList.Count <= 0) + return; + + unchecked + { + var outputData = new byte[100]; + var outIdx = BeginPacket(outputData); + Array.Copy(BitConverter.GetBytes((uint)MessageType.DSUS_PadDataRsp), 0, outputData, outIdx, 4); + outIdx += 4; + + outputData[outIdx++] = padMeta.PadId; + outputData[outIdx++] = (byte)padMeta.PadState; + outputData[outIdx++] = (byte)padMeta.Model; + outputData[outIdx++] = (byte)padMeta.ConnectionType; + { + var padMac = padMeta.PadMacAddress.GetAddressBytes(); + outputData[outIdx++] = padMac[0]; + outputData[outIdx++] = padMac[1]; + outputData[outIdx++] = padMac[2]; + outputData[outIdx++] = padMac[3]; + outputData[outIdx++] = padMac[4]; + outputData[outIdx++] = padMac[5]; + } + outputData[outIdx++] = (byte)padMeta.BatteryStatus; + outputData[outIdx++] = padMeta.IsActive ? (byte)1 : (byte)0; + + Array.Copy(BitConverter.GetBytes((uint)udpPacketCount++), 0, outputData, outIdx, 4); + outIdx += 4; + + if (!ReportToBuffer(outputData, ref outIdx, padIdx)) + return; + FinishPacket(outputData); + + foreach (var cl in clientsList) + { + int temp = 0; + poolLock.EnterWriteLock(); + temp = listInd; + listInd = ++listInd % ARG_BUFFER_LEN; + SocketAsyncEventArgs args = new SocketAsyncEventArgs() + { + RemoteEndPoint = cl, + }; + args.SetBuffer(dataBuffers[temp], 0, 100); + args.Completed += SocketEvent_AsyncCompleted; + poolLock.ExitWriteLock(); + + _pool.Wait(); + Array.Copy(outputData, args.Buffer, outputData.Length); + bool sentAsync = false; + try + { + bool sendAsync = udpSock.SendToAsync(args); + } + catch (SocketException /*ex*/) { } + catch (Exception /*ex*/) { } + finally + { + if (!sentAsync) CompletedSynchronousSocketEvent(args); + } + } + } + + clientsList.Clear(); + clientsList = null; } } } \ No newline at end of file diff --git a/HandheldCompanion/DSU/DualShockPadMeta.cs b/HandheldCompanion/DSU/DualShockPadMeta.cs new file mode 100644 index 000000000..9110c1212 --- /dev/null +++ b/HandheldCompanion/DSU/DualShockPadMeta.cs @@ -0,0 +1,15 @@ +using System.Net.NetworkInformation; + +namespace HandheldCompanion.DSU +{ + public struct DualShockPadMeta + { + public byte PadId; + public DsState PadState; + public DsConnection ConnectionType; + public DsModel Model; + public PhysicalAddress PadMacAddress; + public DsBattery BatteryStatus; + public bool IsActive; + } +} diff --git a/HandheldCompanion/DSU/MessageType.cs b/HandheldCompanion/DSU/MessageType.cs new file mode 100644 index 000000000..51b46a0b9 --- /dev/null +++ b/HandheldCompanion/DSU/MessageType.cs @@ -0,0 +1,12 @@ +namespace HandheldCompanion.DSU +{ + public enum MessageType + { + DSUC_VersionReq = 0x100000, + DSUS_VersionRsp = 0x100000, + DSUC_ListPorts = 0x100001, + DSUS_PortInfo = 0x100001, + DSUC_PadDataReq = 0x100002, + DSUS_PadDataRsp = 0x100002 + } +} diff --git a/HandheldCompanion/Devices/ASUS/ROGAlly.cs b/HandheldCompanion/Devices/ASUS/ROGAlly.cs index 38f4a3fb7..97fc37f66 100644 --- a/HandheldCompanion/Devices/ASUS/ROGAlly.cs +++ b/HandheldCompanion/Devices/ASUS/ROGAlly.cs @@ -30,6 +30,8 @@ public class ROGAlly : IDevice private AsusACPI? asusACPI; + private static bool customFanControl = false; + private const byte INPUT_HID_ID = 0x5a; private const byte AURA_HID_ID = 0x5d; private const int ASUS_ID = 0x0b05; @@ -430,8 +432,17 @@ public override void SetFanControl(bool enable, int mode = 0) switch (enable) { case false: - asusACPI?.DeviceSet(AsusACPI.PerformanceMode, mode); - return; + { + if (customFanControl) + { + customFanControl = false; + asusACPI?.DeviceSet(AsusACPI.PerformanceMode, mode); + } + } + break; + case true: + customFanControl = true; + break; } } @@ -724,7 +735,7 @@ public void SetBatteryChargeLimit(int chargeLimit) asusACPI?.DeviceSet(AsusACPI.BatteryLimit, chargeLimit); } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Devices/AYANEO/AYANEOFlipDS.cs b/HandheldCompanion/Devices/AYANEO/AYANEOFlipDS.cs index 522f0060e..e8c476f78 100644 --- a/HandheldCompanion/Devices/AYANEO/AYANEOFlipDS.cs +++ b/HandheldCompanion/Devices/AYANEO/AYANEOFlipDS.cs @@ -49,7 +49,7 @@ private void ControllerManager_InputsUpdated(ControllerState Inputs) } } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2AMD.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2-2022-6800U.cs similarity index 78% rename from HandheldCompanion/Devices/GPD/GPDWinMax2AMD.cs rename to HandheldCompanion/Devices/GPD/GPDWinMax2-2022-6800U.cs index 6d0321fd4..5ab46f823 100644 --- a/HandheldCompanion/Devices/GPD/GPDWinMax2AMD.cs +++ b/HandheldCompanion/Devices/GPD/GPDWinMax2-2022-6800U.cs @@ -3,13 +3,13 @@ namespace HandheldCompanion.Devices; -public class GPDWinMax2AMD : GPDWinMax2 +public class GPDWinMax2_2022_6800U : GPDWinMax2 { - public GPDWinMax2AMD() + public GPDWinMax2_2022_6800U() { // https://www.amd.com/fr/products/apu/amd-ryzen-7-6800u nTDP = new double[] { 15, 15, 28 }; - cTDP = new double[] { 15, 28 }; + cTDP = new double[] { 3, 28 }; GfxClock = new double[] { 100, 2200 }; CpuClock = 4700; @@ -20,8 +20,7 @@ public GPDWinMax2AMD() { 'Y', 'Z' }, { 'Z', 'X' } }; - - AccelerometerAxis = new Vector3(-1.0f, 1.0f, 1.0f); + AccelerometerAxis = new Vector3(-1.0f, -1.0f, 1.0f); AccelerometerAxisSwap = new SortedDictionary { { 'X', 'X' }, diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2-2023-7640U.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2-2023-7640U.cs new file mode 100644 index 000000000..eb9aaa694 --- /dev/null +++ b/HandheldCompanion/Devices/GPD/GPDWinMax2-2023-7640U.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace HandheldCompanion.Devices; + +public class GPDWinMax2_2023_7640U : GPDWinMax2 +{ + public GPDWinMax2_2023_7640U() + { + // https://www.amd.com/en/products/processors/laptop/ryzen/7000-series/amd-ryzen-5-7640u.html + nTDP = new double[] { 15, 15, 28 }; + cTDP = new double[] { 3, 28 }; + GfxClock = new double[] { 100, 2600 }; + CpuClock = 4900; + + GyrometerAxis = new Vector3(1.0f, -1.0f, -1.0f); + GyrometerAxisSwap = new SortedDictionary + { + { 'X', 'Y' }, + { 'Y', 'Z' }, + { 'Z', 'X' } + }; + + AccelerometerAxis = new Vector3(-1.0f, -1.0f, 1.0f); + AccelerometerAxisSwap = new SortedDictionary + { + { 'X', 'X' }, + { 'Y', 'Z' }, + { 'Z', 'Y' } + }; + } +} \ No newline at end of file diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2-2023-7840U.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2-2023-7840U.cs new file mode 100644 index 000000000..5f6bd2ad5 --- /dev/null +++ b/HandheldCompanion/Devices/GPD/GPDWinMax2-2023-7840U.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace HandheldCompanion.Devices; + +public class GPDWinMax2_2023_7840U : GPDWinMax2 +{ + public GPDWinMax2_2023_7840U() + { + // https://www.amd.com/en/products/processors/laptop/ryzen/7000-series/amd-ryzen-7-7840u.html + nTDP = new double[] { 15, 15, 28 }; + cTDP = new double[] { 3, 28 }; + GfxClock = new double[] { 100, 2700 }; + CpuClock = 5200; + + GyrometerAxis = new Vector3(1.0f, -1.0f, -1.0f); + GyrometerAxisSwap = new SortedDictionary + { + { 'X', 'Y' }, + { 'Y', 'Z' }, + { 'Z', 'X' } + }; + + AccelerometerAxis = new Vector3(-1.0f, -1.0f, 1.0f); + AccelerometerAxisSwap = new SortedDictionary + { + { 'X', 'X' }, + { 'Y', 'Z' }, + { 'Z', 'Y' } + }; + } +} \ No newline at end of file diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2-2024-8640U.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2-2024-8640U.cs new file mode 100644 index 000000000..5b9a88201 --- /dev/null +++ b/HandheldCompanion/Devices/GPD/GPDWinMax2-2024-8640U.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace HandheldCompanion.Devices; + +public class GPDWinMax2_2024_8640U : GPDWinMax2 +{ + public GPDWinMax2_2024_8640U() + { + // https://www.amd.com/en/products/processors/laptop/ryzen/8000-series/amd-ryzen-5-8640u.html + nTDP = new double[] { 15, 15, 28 }; + cTDP = new double[] { 3, 28 }; + GfxClock = new double[] { 100, 2600 }; + CpuClock = 4900; + + GyrometerAxis = new Vector3(1.0f, -1.0f, -1.0f); + GyrometerAxisSwap = new SortedDictionary + { + { 'X', 'Y' }, + { 'Y', 'Z' }, + { 'Z', 'X' } + }; + + AccelerometerAxis = new Vector3(-1.0f, -1.0f, 1.0f); + AccelerometerAxisSwap = new SortedDictionary + { + { 'X', 'X' }, + { 'Y', 'Z' }, + { 'Z', 'Y' } + }; + } +} \ No newline at end of file diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2-2024-8840U.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2-2024-8840U.cs new file mode 100644 index 000000000..830fca97e --- /dev/null +++ b/HandheldCompanion/Devices/GPD/GPDWinMax2-2024-8840U.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace HandheldCompanion.Devices; + +public class GPDWinMax2_2024_8840U : GPDWinMax2 +{ + public GPDWinMax2_2024_8840U() + { + // https://www.amd.com/en/products/processors/laptop/ryzen/8000-series/amd-ryzen-7-8840u.html + nTDP = new double[] { 15, 15, 28 }; + cTDP = new double[] { 3, 28 }; + GfxClock = new double[] { 100, 2700 }; + CpuClock = 5200; + + GyrometerAxis = new Vector3(1.0f, -1.0f, -1.0f); + GyrometerAxisSwap = new SortedDictionary + { + { 'X', 'Y' }, + { 'Y', 'Z' }, + { 'Z', 'X' } + }; + + AccelerometerAxis = new Vector3(-1.0f, -1.0f, 1.0f); + AccelerometerAxisSwap = new SortedDictionary + { + { 'X', 'X' }, + { 'Y', 'Z' }, + { 'Z', 'Y' } + }; + } +} \ No newline at end of file diff --git a/HandheldCompanion/Devices/GPD/GPDWinMax2.cs b/HandheldCompanion/Devices/GPD/GPDWinMax2.cs index c57cf8d8c..26b4c57c8 100644 --- a/HandheldCompanion/Devices/GPD/GPDWinMax2.cs +++ b/HandheldCompanion/Devices/GPD/GPDWinMax2.cs @@ -1,59 +1,59 @@ -using HandheldCompanion.Inputs; -using WindowsInput.Events; - -namespace HandheldCompanion.Devices; - -public class GPDWinMax2 : IDevice -{ - public GPDWinMax2() - { - // device specific settings - ProductIllustration = "device_gpd_winmax2"; - - // device specific capacities - Capabilities = DeviceCapabilities.FanControl; - - ECDetails = new ECDetails - { - 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. - OEMChords.Add(new KeyboardChord("Menu", - [KeyCode.LButton | KeyCode.XButton2], - [KeyCode.LButton | KeyCode.XButton2], - true, ButtonFlags.OEM1 - )); - - // note, need to manually configured in GPD app - OEMChords.Add(new KeyboardChord("Bottom button left", - [KeyCode.F11, KeyCode.L], - [KeyCode.F11, KeyCode.L], - false, ButtonFlags.OEM2 - )); - - OEMChords.Add(new KeyboardChord("Bottom button right", - [KeyCode.F12, KeyCode.R], - [KeyCode.F12, KeyCode.R], - false, ButtonFlags.OEM3 - )); +using HandheldCompanion.Inputs; +using WindowsInput.Events; + +namespace HandheldCompanion.Devices; + +public class GPDWinMax2 : IDevice +{ + public GPDWinMax2() + { + // device specific settings + ProductIllustration = "device_gpd_winmax2"; + + // device specific capacities + Capabilities = DeviceCapabilities.FanControl; + + ECDetails = new ECDetails + { + 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. + OEMChords.Add(new KeyboardChord("Menu", + [KeyCode.LButton | KeyCode.XButton2], + [KeyCode.LButton | KeyCode.XButton2], + true, ButtonFlags.OEM1 + )); + + // note, need to manually configured in GPD app + OEMChords.Add(new KeyboardChord("Bottom button left", + [KeyCode.F11, KeyCode.L], + [KeyCode.F11, KeyCode.L], + false, ButtonFlags.OEM2 + )); + + OEMChords.Add(new KeyboardChord("Bottom button right", + [KeyCode.F12, KeyCode.R], + [KeyCode.F12, KeyCode.R], + false, ButtonFlags.OEM3 + )); } - public override string GetGlyph(ButtonFlags button) - { - switch (button) - { - case ButtonFlags.OEM2: - return "\u220E"; - case ButtonFlags.OEM3: - return "\u220F"; - } - - return defaultGlyph; - } + 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/IDevice.cs b/HandheldCompanion/Devices/IDevice.cs index 82681834e..7336cd3da 100644 --- a/HandheldCompanion/Devices/IDevice.cs +++ b/HandheldCompanion/Devices/IDevice.cs @@ -146,7 +146,7 @@ public abstract class IDevice public string ProductModel = "default"; // minimum delay before trying to emulate a virtual controller on system resume (milliseconds) - public short ResumeDelay = 1000; + public short ResumeDelay = 2000; // key press delay to use for certain scenarios public short KeyPressDelay = 20; @@ -397,7 +397,28 @@ public static IDevice GetCurrent() device = new GPDWinMax2Intel(); break; case "G1619-04": - device = new GPDWinMax2AMD(); + switch (Processor) + { + case "AMD Ryzen 7 6800U with Radeon Graphics": + device = new GPDWinMax2_2022_6800U(); + break; + case "AMD Ryzen 5 7640U w/ Radeon 760M Graphics": + device = new GPDWinMax2_2023_7640U(); + break; + case "AMD Ryzen 7 7840U w/ Radeon 780M Graphics": + device = new GPDWinMax2_2023_7840U(); + break; + case "AMD Ryzen 5 8640U w/ Radeon 760M Graphics": + device = new GPDWinMax2_2024_8640U(); + break; + case "AMD Ryzen 7 8840U w/ Radeon 780M Graphics": + device = new GPDWinMax2_2024_8840U(); + break; + // if none of those + default: + device = new GPDWinMax2_2024_8840U(); // Assume newer device is similar to latest as of this commit + break; + } break; } } diff --git a/HandheldCompanion/Devices/Lenovo/LegionGo.cs b/HandheldCompanion/Devices/Lenovo/LegionGo.cs index abef4e090..305c4405f 100644 --- a/HandheldCompanion/Devices/Lenovo/LegionGo.cs +++ b/HandheldCompanion/Devices/Lenovo/LegionGo.cs @@ -534,7 +534,7 @@ public override string GetGlyph(ButtonFlags button) return defaultGlyph; } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Devices/Valve/SteamDeck.cs b/HandheldCompanion/Devices/Valve/SteamDeck.cs index 3ced6f085..ca0adad1d 100644 --- a/HandheldCompanion/Devices/Valve/SteamDeck.cs +++ b/HandheldCompanion/Devices/Valve/SteamDeck.cs @@ -246,7 +246,7 @@ public void SetMaxBatteryCharge(int chargeLimit) inpOut?.WriteMemory(MCBL, data); } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/HandheldCompanion.csproj b/HandheldCompanion/HandheldCompanion.csproj index 3ab4ad427..3fb8f3688 100644 --- a/HandheldCompanion/HandheldCompanion.csproj +++ b/HandheldCompanion/HandheldCompanion.csproj @@ -2,7 +2,7 @@ WinExe - net8.0-windows10.0.19041.0 + net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0 true true x64 @@ -12,7 +12,7 @@ HandheldCompanion.App $(SolutionDir)bin\$(Configuration) Resources\icon.ico - 0.21.6.1 + 0.21.7.0 app.manifest AnyCPU;x64;x86 true @@ -229,9 +229,11 @@ + + diff --git a/HandheldCompanion/Inputs/ButtonFlags.cs b/HandheldCompanion/Inputs/ButtonFlags.cs index 39d3d0aad..48fa8c5ef 100644 --- a/HandheldCompanion/Inputs/ButtonFlags.cs +++ b/HandheldCompanion/Inputs/ButtonFlags.cs @@ -87,6 +87,9 @@ public enum ButtonFlags : byte [Description("Volume Down")] VolumeDown = 61, Special2 = 62, + B9 = 63, + B10 = 64, + B11 = 65, HOTKEY_START = 70, HOTKEY_GYRO_ACTIVATION = 140, diff --git a/HandheldCompanion/Inputs/GyroState.cs b/HandheldCompanion/Inputs/GyroState.cs index 669382392..d3615ca8e 100644 --- a/HandheldCompanion/Inputs/GyroState.cs +++ b/HandheldCompanion/Inputs/GyroState.cs @@ -9,12 +9,10 @@ public class GyroState : ICloneable public Dictionary Accelerometer = []; public Dictionary Gyroscope = []; - public const byte SENSOR_MAX = 4; public enum SensorState { - Raw, Default, - GMH, + GamepadMotion, DSU } diff --git a/HandheldCompanion/Managers/ControllerManager.cs b/HandheldCompanion/Managers/ControllerManager.cs index 5c478f57a..f7c569cfb 100644 --- a/HandheldCompanion/Managers/ControllerManager.cs +++ b/HandheldCompanion/Managers/ControllerManager.cs @@ -55,7 +55,7 @@ public static class ControllerManager private static object targetLock = new object(); public static ControllerManagerStatus managerStatus = ControllerManagerStatus.Pending; - private static Timer scenarioTimer = new(1000) { AutoReset = true }; + private static Timer scenarioTimer = new(100) { AutoReset = false }; public static bool IsInitialized; @@ -84,7 +84,7 @@ public static Task Start() DriversStore = DeserializeDriverStore(); // Flushing possible JoyShocks... - JslDisconnectAndDisposeAll(); + JslDisconnect(); DeviceManager.XUsbDeviceArrived += XUsbDeviceArrived; DeviceManager.XUsbDeviceRemoved += XUsbDeviceRemoved; @@ -95,10 +95,8 @@ public static Task Start() SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; - /* UIGamepad.GotFocus += GamepadFocusManager_GotFocus; UIGamepad.LostFocus += GamepadFocusManager_LostFocus; - */ ProcessManager.ForegroundChanged += ProcessManager_ForegroundChanged; @@ -152,7 +150,7 @@ public static void Stop() controller.Unhide(false); // Flushing possible JoyShocks... - JslDisconnectAndDisposeAll(); + JslDisconnect(); LogManager.LogInformation("{0} has stopped", "ControllerManager"); } @@ -173,7 +171,6 @@ private enum FocusedWindow Quicktools } - /* private static void GamepadFocusManager_LostFocus(string Name) { switch (Name) @@ -207,7 +204,6 @@ private static void GamepadFocusManager_GotFocus(string Name) // check applicable scenarios CheckControllerScenario(); } - */ private static void ProcessManager_ForegroundChanged(ProcessEx? processEx, ProcessEx? backgroundEx) { @@ -281,11 +277,9 @@ private static void ScenarioTimer_Elapsed(object? sender, ElapsedEventArgs e) } } - /* // either main window or quicktools are focused if (focusedWindows != FocusedWindow.None) ControllerMuted = true; - */ } private static void CheckControllerScenario() @@ -295,7 +289,7 @@ private static void CheckControllerScenario() scenarioTimer.Start(); } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { @@ -594,8 +588,7 @@ private static async void HidDeviceArrived(PnPDetails details, DeviceEventArgs o // unsupported controller if (controller is null) { - LogManager.LogError("Unsupported Generic controller: VID:{0} and PID:{1}", details.GetVendorID(), - details.GetProductID()); + LogManager.LogError("Unsupported Generic controller: VID:{0} and PID:{1}", details.GetVendorID(), details.GetProductID()); return; } @@ -845,10 +838,36 @@ private static async void XUsbDeviceArrived(PnPDetails details, DeviceEventArgs case "0x17EF": controller = new LegionController(details); break; + + // GameSir + case "0x3537": + { + switch (details.GetProductID()) + { + // Tarantula Pro (Dongle) + case "0x1099": + case "0x103E": + details.isDongle = true; + goto case "0x1050"; + // Tarantula Pro + default: + case "0x1050": + controller = new TatantulaProController(details); + break; + } + } + break; } }); } + // unsupported controller + if (controller is null) + { + LogManager.LogError("Unsupported XInput controller: VID:{0} and PID:{1}", details.GetVendorID(), details.GetProductID()); + return; + } + while (!controller.IsReady && controller.IsConnected()) await Task.Delay(250); @@ -1184,11 +1203,14 @@ public static List GetControllers() } private static ControllerState mutedState = new ControllerState(); - private static void UpdateInputs(ControllerState controllerState, GamepadMotion gamepadMotion, float deltaTimeSeconds) + private static void UpdateInputs(ControllerState controllerState, Dictionary gamepadMotions, float deltaTimeSeconds, byte gamepadIndex) { // raise event InputsUpdated?.Invoke(controllerState); + // get main motion + GamepadMotion gamepadMotion = gamepadMotions[gamepadIndex]; + switch (sensorSelection) { case SensorFamily.Windows: @@ -1205,21 +1227,19 @@ private static void UpdateInputs(ControllerState controllerState, GamepadMotion MainWindow.overlayModel.UpdateReport(controllerState, gamepadMotion, deltaTimeSeconds); } + // compute layout + controllerState = LayoutManager.MapController(controllerState); + InputsUpdated2?.Invoke(controllerState); + // controller is muted if (ControllerMuted) { mutedState.ButtonState[ButtonFlags.Special] = controllerState.ButtonState[ButtonFlags.Special]; - - // swap states controllerState = mutedState; } - else - { - // compute layout - controllerState = LayoutManager.MapController(controllerState); - InputsUpdated2?.Invoke(controllerState); - } + DS4Touch.UpdateInputs(controllerState); + DSUServer.UpdateInputs(controllerState, gamepadMotions); VirtualManager.UpdateInputs(controllerState, gamepadMotion); } diff --git a/HandheldCompanion/Managers/DynamicLightingManager.cs b/HandheldCompanion/Managers/DynamicLightingManager.cs index fab7028f1..1cd6faaf0 100644 --- a/HandheldCompanion/Managers/DynamicLightingManager.cs +++ b/HandheldCompanion/Managers/DynamicLightingManager.cs @@ -146,7 +146,7 @@ private static void ReleaseDirect3DDevice() device = null; } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Managers/HotkeysManager.cs b/HandheldCompanion/Managers/HotkeysManager.cs index 1c826363e..bd423fa1f 100644 --- a/HandheldCompanion/Managers/HotkeysManager.cs +++ b/HandheldCompanion/Managers/HotkeysManager.cs @@ -1,6 +1,8 @@ using HandheldCompanion.Commands; using HandheldCompanion.Commands.Functions.HC; using HandheldCompanion.Commands.Functions.Multimedia; +using HandheldCompanion.Commands.Functions.Multitasking; +using HandheldCompanion.Commands.Functions.Performance; using HandheldCompanion.Commands.Functions.Windows; using HandheldCompanion.Inputs; using HandheldCompanion.Utils; @@ -186,16 +188,16 @@ private static void ProcessHotkey(string fileName) command = new OverlayTrackpadCommands(); break; case 03: - // "OnScreenDisplayToggle" + command = new QuickOverlayCommands(); break; case 10: command = new QuickToolsCommands(); break; case 11: - // "increaseTDP" + command = new TDPIncrease(); break; case 12: - // "decreaseTDP" + command = new TDPDecrease(); break; case 13: // "suspendResumeTask" diff --git a/HandheldCompanion/Managers/InputsManager.cs b/HandheldCompanion/Managers/InputsManager.cs index b72e5adbd..7c799e5d6 100644 --- a/HandheldCompanion/Managers/InputsManager.cs +++ b/HandheldCompanion/Managers/InputsManager.cs @@ -180,7 +180,7 @@ private static bool CheckForSequence(bool IsKeyDown, bool IsKeyUp) } // execute command - hotkey.command.Execute(IsKeyDown, IsKeyUp); + hotkey.Execute(IsKeyDown, IsKeyUp, false); // raise event CommandExecuted?.Invoke(hotkey, hotkey.command); diff --git a/HandheldCompanion/Managers/LayoutManager.cs b/HandheldCompanion/Managers/LayoutManager.cs index eab93b252..c618aca4e 100644 --- a/HandheldCompanion/Managers/LayoutManager.cs +++ b/HandheldCompanion/Managers/LayoutManager.cs @@ -236,7 +236,7 @@ public static void SerializeLayoutTemplate(LayoutTemplate layoutTemplate) File.WriteAllText(fileName, jsonString); } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Managers/MotionManager.cs b/HandheldCompanion/Managers/MotionManager.cs index d5269d610..05ee69250 100644 --- a/HandheldCompanion/Managers/MotionManager.cs +++ b/HandheldCompanion/Managers/MotionManager.cs @@ -6,6 +6,7 @@ using HandheldCompanion.Utils; using HandheldCompanion.Views; using System; +using System.Collections.Generic; using System.Numerics; using static HandheldCompanion.Utils.DeviceUtils; using SensorState = HandheldCompanion.Inputs.GyroState.SensorState; @@ -53,72 +54,121 @@ public static void UpdateReport(ControllerState controllerState, GamepadMotion g // and is enough for DS4/DSU gyroscope handling private static void SetupMotion(ControllerState controllerState, GamepadMotion gamepadMotion) { - Profile current = ProfileManager.GetCurrent(); + // GamepadMotion: calibrated/filtered outputs from JoyShockLibrary + if (controllerState.GyroState.Gyroscope.TryGetValue(SensorState.GamepadMotion, out Vector3 gyrometer)) + { + gamepadMotion.GetCalibratedGyro(out float gyroX, out float gyroY, out float gyroZ); + + gyrometer.X = gyroX; + gyrometer.Y = gyroZ; + gyrometer.Z = gyroY; + } + if (controllerState.GyroState.Accelerometer.TryGetValue(SensorState.GamepadMotion, out Vector3 acceleromter)) + { + gamepadMotion.GetGravity(out float accelX, out float accelY, out float accelZ); - // GMH: based on GamepadMotionHelpers values - gamepadMotion.GetCalibratedGyro(out float gyroX, out float gyroY, out float gyroZ); - gamepadMotion.GetGravity(out float accelX, out float accelY, out float accelZ); + acceleromter.X = accelX; + acceleromter.Y = accelY; + acceleromter.Z = accelZ; + } - controllerState.GyroState.Gyroscope[SensorState.GMH] = new() { X = gyroX, Y = gyroY, Z = gyroZ }; - controllerState.GyroState.Accelerometer[SensorState.GMH] = new() { X = accelX, Y = accelY, Z = accelZ }; + // DSU: unfiltered outputs from sensors + if (controllerState.GyroState.Gyroscope.TryGetValue(SensorState.DSU, out gyrometer)) + { + gamepadMotion.GetRawGyro(out float gyroX, out float gyroY, out float gyroZ); - // Default: based on GamepadMotionHelpers values with multipliers applied - controllerState.GyroState.Gyroscope[SensorState.Default] = controllerState.GyroState.Gyroscope[SensorState.GMH] * current.GyrometerMultiplier; - controllerState.GyroState.Accelerometer[SensorState.Default] = controllerState.GyroState.Accelerometer[SensorState.GMH] * current.AccelerometerMultiplier; + gyrometer.X = gyroX; + gyrometer.Y = gyroZ; + gyrometer.Z = gyroY; + } + if (controllerState.GyroState.Accelerometer.TryGetValue(SensorState.DSU, out acceleromter)) + { + gamepadMotion.GetRawAcceleration(out float accelX, out float accelY, out float accelZ); + + acceleromter.X = accelX; + acceleromter.Y = accelY; + acceleromter.Z = accelZ; + } + + // Default: based on GamepadMotionHelpers values with profile settings applied + Profile current = ProfileManager.GetCurrent(); + + controllerState.GyroState.Gyroscope[SensorState.Default] = controllerState.GyroState.Gyroscope[SensorState.GamepadMotion] * current.GyrometerMultiplier; + controllerState.GyroState.Accelerometer[SensorState.Default] = controllerState.GyroState.Accelerometer[SensorState.GamepadMotion] * current.AccelerometerMultiplier; // Default: swap roll/yaw/auto + SteeringAxis steeringAxis = DetermineSteeringAxis(current, controllerState); + if (steeringAxis == SteeringAxis.Yaw) + { + SwapYawRoll(controllerState.GyroState.Gyroscope, SensorState.Default); + SwapYawRoll(controllerState.GyroState.Accelerometer, SensorState.Default); + SwapYawRoll(controllerState.GyroState.Gyroscope, SensorState.DSU); + SwapYawRoll(controllerState.GyroState.Accelerometer, SensorState.DSU); + } + + // DSU: invert axes if needed + if (current.MotionInvertHorizontal) + { + InvertAxis(controllerState.GyroState.Gyroscope, SensorState.DSU, Axis.Y, Axis.Z); + InvertAxis(controllerState.GyroState.Accelerometer, SensorState.DSU, Axis.Y, Axis.Z); + } + if (current.MotionInvertVertical) + { + InvertAxis(controllerState.GyroState.Gyroscope, SensorState.DSU, Axis.X, Axis.Y); + InvertAxis(controllerState.GyroState.Accelerometer, SensorState.DSU, Axis.X, Axis.Y); + } + } + + private static SteeringAxis DetermineSteeringAxis(Profile current, ControllerState controllerState) + { SteeringAxis steeringAxis = current.SteeringAxis; if (steeringAxis == SteeringAxis.Auto) { SensorFamily sensorSelection = (SensorFamily)SettingsManager.GetInt("SensorSelection"); - switch (sensorSelection) + if (sensorSelection == SensorFamily.Windows || sensorSelection == SensorFamily.SerialUSBIMU) { - case SensorFamily.Windows: - case SensorFamily.SerialUSBIMU: - steeringAxis = SteeringAxis.Yaw; - break; - - case SensorFamily.Controller: - if (Math.Abs(accelZ) > Math.Abs(accelY)) - steeringAxis = SteeringAxis.Yaw; - break; + return SteeringAxis.Yaw; + } + if (sensorSelection == SensorFamily.Controller && + Math.Abs(controllerState.GyroState.Accelerometer[SensorState.Default].Z) > Math.Abs(controllerState.GyroState.Accelerometer[SensorState.Default].Y)) + { + return SteeringAxis.Yaw; } } + return steeringAxis; + } - switch (steeringAxis) + private static void SwapYawRoll(Dictionary sensorDictionary, SensorState state) + { + if (sensorDictionary.TryGetValue(state, out Vector3 sensor)) { - case SteeringAxis.Yaw: - controllerState.GyroState.Gyroscope[SensorState.Default] = new(controllerState.GyroState.Gyroscope[SensorState.Default].X, -controllerState.GyroState.Gyroscope[SensorState.Default].Z, -controllerState.GyroState.Gyroscope[SensorState.Default].Y); - controllerState.GyroState.Accelerometer[SensorState.Default] = new(controllerState.GyroState.Accelerometer[SensorState.Default].X, -controllerState.GyroState.Accelerometer[SensorState.Default].Z, -controllerState.GyroState.Accelerometer[SensorState.Default].Y); - break; + sensor = new Vector3(sensor.X, -sensor.Z, -sensor.Y); + sensorDictionary[state] = sensor; } + } - // update all states (except Default, GMH) - foreach (SensorState state in Enum.GetValues(typeof(SensorState))) + private static void InvertAxis(Dictionary sensorDictionary, SensorState state, Axis axis1, Axis axis2) + { + if (sensorDictionary.TryGetValue(state, out Vector3 sensor)) { - if (state == SensorState.Default || state == SensorState.GMH) - continue; - - // set to default - controllerState.GyroState.Gyroscope[state] = controllerState.GyroState.Gyroscope[SensorState.Default]; - controllerState.GyroState.Accelerometer[state] = controllerState.GyroState.Accelerometer[SensorState.Default]; - - // DSU: invert horizontal axis - if (current.MotionInvertHorizontal) + switch (axis1) { - controllerState.GyroState.Gyroscope[state] = new(controllerState.GyroState.Gyroscope[state].X, -controllerState.GyroState.Gyroscope[state].Y, -controllerState.GyroState.Gyroscope[state].Z); - controllerState.GyroState.Accelerometer[state] = new(controllerState.GyroState.Accelerometer[state].X, -controllerState.GyroState.Accelerometer[state].Y, -controllerState.GyroState.Accelerometer[state].Z); + case Axis.X: sensor.X = -sensor.X; break; + case Axis.Y: sensor.Y = -sensor.Y; break; + case Axis.Z: sensor.Z = -sensor.Z; break; } - - // DSU: invert vertical axis - if (current.MotionInvertVertical) + switch (axis2) { - controllerState.GyroState.Gyroscope[state] = new(-controllerState.GyroState.Gyroscope[state].X, -controllerState.GyroState.Gyroscope[state].Y, controllerState.GyroState.Gyroscope[state].Z); - controllerState.GyroState.Accelerometer[state] = new(-controllerState.GyroState.Accelerometer[state].X, -controllerState.GyroState.Accelerometer[state].Y, controllerState.GyroState.Accelerometer[state].Z); + case Axis.X: sensor.X = -sensor.X; break; + case Axis.Y: sensor.Y = -sensor.Y; break; + case Axis.Z: sensor.Z = -sensor.Z; break; } + sensorDictionary[state] = sensor; } } + private enum Axis { X, Y, Z } + // this function is used for advanced motion calculations used by // gyro to joy/mouse mappings and by UI that configures them private static void ProcessMotion(ControllerState controllerState, GamepadMotion gamepadMotion) @@ -230,6 +280,4 @@ private static void ProcessMotion(ControllerState controllerState, GamepadMotion controllerState.AxisState[AxisFlags.GyroY] = (short)Math.Clamp(output.Y, short.MinValue, short.MaxValue); } } - - } \ No newline at end of file diff --git a/HandheldCompanion/Managers/MultimediaManager.cs b/HandheldCompanion/Managers/MultimediaManager.cs index be0d565f1..919eb6335 100644 --- a/HandheldCompanion/Managers/MultimediaManager.cs +++ b/HandheldCompanion/Managers/MultimediaManager.cs @@ -90,7 +90,7 @@ private static void SetDefaultAudioEndPoint() } } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { @@ -479,6 +479,38 @@ public static void DecreaseVolume() SetVolume(volume); } + public static void Mute() + { + if (!VolumeSupport) + return; + + multimediaDevice.AudioEndpointVolume.Mute = true; + } + + public static void Unmute() + { + if (!VolumeSupport) + return; + + multimediaDevice.AudioEndpointVolume.Mute = false; + } + + public static void ToggleMute() + { + if (!VolumeSupport) + return; + + multimediaDevice.AudioEndpointVolume.Mute = !multimediaDevice.AudioEndpointVolume.Mute; + } + + public static bool IsMuted() + { + if (!VolumeSupport) + return true; + + return multimediaDevice.AudioEndpointVolume.Mute; + } + public static short GetBrightness() { try diff --git a/HandheldCompanion/Managers/OSDManager.cs b/HandheldCompanion/Managers/OSDManager.cs index b9d548080..78489ab7d 100644 --- a/HandheldCompanion/Managers/OSDManager.cs +++ b/HandheldCompanion/Managers/OSDManager.cs @@ -413,7 +413,7 @@ private static void AddElementIfNotNull(OverlayEntry entry, float? value, String } } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Managers/PerformanceManager.cs b/HandheldCompanion/Managers/PerformanceManager.cs index b9aaf243b..6201d4ab6 100644 --- a/HandheldCompanion/Managers/PerformanceManager.cs +++ b/HandheldCompanion/Managers/PerformanceManager.cs @@ -22,8 +22,7 @@ public static class OSPowerMode /// /// Better Performance mode. /// - // public static Guid BetterPerformance = new Guid("3af9B8d9-7c97-431d-ad78-34a8bfea439f"); - public static Guid BetterPerformance = new(); + public static Guid BetterPerformance = Guid.Empty; /// /// Best Performance mode. @@ -43,95 +42,108 @@ public enum CPUBoostLevel public static class PerformanceManager { private const short INTERVAL_DEFAULT = 3000; // default interval between value scans - private const short INTERVAL_AUTO = 1010; // default interval between value scans + private const short INTERVAL_AUTO = 1010; // default interval between value scans for AutoTDP private const short INTERVAL_DEGRADED = 5000; // degraded interval between value scans - public static readonly Guid[] PowerModes = new Guid[3] { OSPowerMode.BetterBattery, OSPowerMode.BetterPerformance, OSPowerMode.BestPerformance }; + private const int COUNTER_DEFAULT = 3; // default counter value + private const int COUNTER_AUTO = 5; // default counter value for AutoTDP - private static readonly Timer autoWatchdog; - private static readonly Timer cpuWatchdog; + public static readonly Guid[] PowerModes = { OSPowerMode.BetterBattery, OSPowerMode.BetterPerformance, OSPowerMode.BestPerformance }; + + private static readonly Timer autotdpWatchdog; + private static readonly Timer tdpWatchdog; private static readonly Timer gfxWatchdog; - private static readonly Timer powerWatchdog; + private static readonly Timer cpuWatchdog; - private static CrossThreadLock autoLock = new(); - private static CrossThreadLock cpuLock = new(); + private static CrossThreadLock autotdpLock = new(); + private static CrossThreadLock tdpLock = new(); private static CrossThreadLock gfxLock = new(); - private static CrossThreadLock powerLock = new(); + private static CrossThreadLock cpuLock = new(); + + private static PowerProfile? currentProfile = null; + + // used to determine relevant TDP and MSR values + private static Processor? processor; // AutoTDP - private static double AutoTDP; - private static double AutoTDPPrev; private static bool AutoTDPFirstRun = true; + private static double AutoTDPTargetFPS; private static int AutoTDPFPSSetpointMetCounter; private static int AutoTDPFPSSmallDipCounter; + private static readonly double[] FPSHistory = new double[6]; + private static double ProcessValueFPSPrevious; + private static double AutoTDP; + private static double AutoTDPPrev; private static double AutoTDPMax; - private static double TDPMax; - private static double TDPMin; - private static double AutoTDPTargetFPS; - private static bool cpuWatchdogPendingStop; - private static uint currentEPP = 50; - private static int currentCoreCount; + private static bool autotdpWatchdogPendingStop; + private static int autotdpWatchdogCounter; // powercfg - private static uint currentPerfBoostMode; private static Guid currentPowerMode = new("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"); - private static readonly double[] CurrentTDP = new double[5]; // used to store current TDP // GPU limits private static double FallbackGfxClock; - private static readonly double[] FPSHistory = new double[6]; - private static bool gfxWatchdogPendingStop; - - private static Processor? processor; - private static double ProcessValueFPSPrevious; private static double StoredGfxClock; + private static bool gfxWatchdogPendingStop; + private static int gfxWatchdogCounter; // TDP limits + private static double TDPMin; + private static double TDPMax; + private static bool tdpWatchdogPendingStop; + private static readonly double[] CurrentTDP = new double[5]; // used to store current TDP private static readonly double[] StoredTDP = new double[3]; // used to store TDP + private static int tdpWatchdogCounter; private static bool IsInitialized; - public static event InitializedEventHandler Initialized; public delegate void InitializedEventHandler(); static PerformanceManager() { // initialize timer(s) - powerWatchdog = new Timer { Interval = INTERVAL_DEFAULT, AutoReset = true, Enabled = false }; - powerWatchdog.Elapsed += powerWatchdog_Elapsed; - cpuWatchdog = new Timer { Interval = INTERVAL_DEFAULT, AutoReset = true, Enabled = false }; cpuWatchdog.Elapsed += cpuWatchdog_Elapsed; + tdpWatchdog = new Timer { Interval = INTERVAL_DEFAULT, AutoReset = true, Enabled = false }; + tdpWatchdog.Elapsed += tdpWatchdog_Elapsed; + gfxWatchdog = new Timer { Interval = INTERVAL_DEFAULT, AutoReset = true, Enabled = false }; gfxWatchdog.Elapsed += gfxWatchdog_Elapsed; - autoWatchdog = new Timer { Interval = INTERVAL_AUTO, AutoReset = true, Enabled = false }; - autoWatchdog.Elapsed += AutoTDPWatchdog_Elapsed; + autotdpWatchdog = new Timer { Interval = INTERVAL_AUTO, AutoReset = true, Enabled = false }; + autotdpWatchdog.Elapsed += autotdpWatchdog_Elapsed; // manage events PowerProfileManager.Applied += PowerProfileManager_Applied; PowerProfileManager.Discarded += PowerProfileManager_Discarded; - SettingsManager.SettingValueChanged += SettingsManagerOnSettingValueChanged; + SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; + } - currentCoreCount = MotherboardInfo.NumberOfCores; + public static double GetMinimumTDP() + { + return TDPMin; + } + + public static double GetMaximumTDP() + { + return TDPMax; } - private static void SettingsManagerOnSettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { case "ConfigurableTDPOverrideDown": { TDPMin = Convert.ToDouble(value); - AutoTDP = (TDPMax + TDPMin) / 2.0d; + if (AutoTDPMax != 0d && AutoTDPMax < TDPMin) AutoTDPMax = TDPMin; } break; case "ConfigurableTDPOverrideUp": { TDPMax = Convert.ToDouble(value); - if (AutoTDPMax == 0d) AutoTDPMax = TDPMax; - AutoTDP = (TDPMax + TDPMin) / 2.0d; + if (AutoTDPMax == 0d || AutoTDPMax > TDPMax) AutoTDPMax = TDPMax; } break; } @@ -139,57 +151,61 @@ private static void SettingsManagerOnSettingValueChanged(string name, object val private static void PowerProfileManager_Applied(PowerProfile profile, UpdateSource source) { + currentProfile = profile; + // apply profile defined TDP if (profile.TDPOverrideEnabled) { if (!profile.AutoTDPEnabled) { - // Manual TDP is set, use it and set max limit + // AutoTDP is off and manual TDP is set + // stop AutoTDP watchdog and apply manual TDP + StopAutoTDPWatchdog(true); RequestTDP(profile.TDPOverrideValues); - if (!cpuWatchdog.Enabled) + if (!tdpWatchdog.Enabled) StartTDPWatchdog(); - - AutoTDPMax = SettingsManager.GetInt("ConfigurableTDPOverrideUp"); } - else if (profile.TDPOverrideValues is not null) + else { - // Both manual TDP and AutoTDP are on, - // use manual slider as the max limit for AutoTDP - AutoTDPMax = profile.TDPOverrideValues[0]; + // Both manual TDP and AutoTDP are on + // use AutoTDP watchdog to adjust TDP StopTDPWatchdog(true); + RestoreTDP(true); } + + // use manual slider as the starting value + // and max limit for AutoTDP + if (profile.TDPOverrideValues is not null) + AutoTDP = AutoTDPMax = profile.TDPOverrideValues[0]; + } else { - if (cpuWatchdog.Enabled) + if (tdpWatchdog.Enabled) StopTDPWatchdog(true); if (!profile.AutoTDPEnabled) { + if (autotdpWatchdog.Enabled) + StopAutoTDPWatchdog(true); + // Neither manual TDP nor AutoTDP is enabled, restore default TDP RestoreTDP(true); } - else - { - // AutoTDP is enabled but manual override is not, use the settings max limit - AutoTDPMax = SettingsManager.GetInt("ConfigurableTDPOverrideUp"); - } + + // manual TDP override is not set + // use the settings max limit for AutoTDP + AutoTDP = AutoTDPMax = SettingsManager.GetInt("ConfigurableTDPOverrideUp"); } // apply profile defined AutoTDP if (profile.AutoTDPEnabled) { AutoTDPTargetFPS = profile.AutoTDPRequestedFPS; - StartAutoTDPWatchdog(); - } - else if (autoWatchdog.Enabled) - { - StopAutoTDPWatchdog(true); - // restore default TDP (if not manual TDP is enabled) - if (!profile.TDPOverrideEnabled) - RestoreTDP(true); + if (!autotdpWatchdog.Enabled) + StartAutoTDPWatchdog(); } // apply profile defined CPU @@ -200,7 +216,7 @@ private static void PowerProfileManager_Applied(PowerProfile profile, UpdateSour else { // restore default GPU clock - RestoreCPUClock(true); + RestoreCPUClock(); } // apply profile defined GPU @@ -209,10 +225,12 @@ private static void PowerProfileManager_Applied(PowerProfile profile, UpdateSour RequestGPUClock(profile.GPUOverrideValue); StartGPUWatchdog(); } - else if (gfxWatchdog.Enabled) + else { + if (gfxWatchdog.Enabled) + StopGPUWatchdog(true); + // restore default GPU clock - StopGPUWatchdog(true); RestoreGPUClock(true); } @@ -221,7 +239,7 @@ private static void PowerProfileManager_Applied(PowerProfile profile, UpdateSour { RequestEPP(profile.EPPOverrideValue); } - else if (currentEPP != 0x00000032) + else { // restore default EPP RequestEPP(0x00000032); @@ -232,7 +250,7 @@ private static void PowerProfileManager_Applied(PowerProfile profile, UpdateSour { RequestCPUCoreCount(profile.CPUCoreCount); } - else if (currentCoreCount != MotherboardInfo.NumberOfCores) + else { // restore default CPU Core Count RequestCPUCoreCount(MotherboardInfo.NumberOfCores); @@ -259,6 +277,8 @@ private static void PowerProfileManager_Applied(PowerProfile profile, UpdateSour private static void PowerProfileManager_Discarded(PowerProfile profile) { + currentProfile = null; + // restore default TDP if (profile.TDPOverrideEnabled) { @@ -270,14 +290,13 @@ private static void PowerProfileManager_Discarded(PowerProfile profile) if (profile.AutoTDPEnabled) { StopAutoTDPWatchdog(true); - StopTDPWatchdog(true); RestoreTDP(true); } // restore default CPU frequency if (profile.CPUOverrideEnabled) { - RestoreCPUClock(true); + RestoreCPUClock(); } // restore default GPU frequency @@ -312,15 +331,17 @@ private static void PowerProfileManager_Discarded(PowerProfile profile) private static void RestoreTDP(bool immediate) { - // On power status change, force refresh TDP + // On power status change, force refresh TDP and AutoTDP PowerProfile profile = PowerProfileManager.GetDefault(); RequestTDP(profile.TDPOverrideValues, immediate); + + if (profile.TDPOverrideValues is not null) + AutoTDP = profile.TDPOverrideValues[0]; } - private static void RestoreCPUClock(bool immediate) + private static void RestoreCPUClock() { - uint maxClock = MotherboardInfo.ProcessorMaxTurboSpeed; - RequestCPUClock(maxClock); + RequestCPUClock(0); } private static void RestoreGPUClock(bool immediate) @@ -328,51 +349,110 @@ private static void RestoreGPUClock(bool immediate) RequestGPUClock(255 * 50, immediate); } - private static void AutoTDPWatchdog_Elapsed(object? sender, ElapsedEventArgs e) + private static void autotdpWatchdog_Elapsed(object? sender, ElapsedEventArgs e) { + if (processor is null || !processor.IsInitialized) + return; + if (!PlatformManager.RTSS.HasHook()) + { + autotdpWatchdog.Interval = INTERVAL_DEGRADED; + RestoreTDP(true); return; + } + else + autotdpWatchdog.Interval = INTERVAL_AUTO; - if (autoLock.TryEnter()) + if (autotdpLock.TryEnter()) { - // todo: Store fps for data gathering from multiple points (OSD, Performance) - double processValueFPS = PlatformManager.RTSS.GetFramerate(true); + try + { + autotdpWatchdogCounter++; - // Ensure realistic process values, prevent divide by 0 - processValueFPS = Math.Clamp(processValueFPS, 5, 500); + bool TDPdone = false; + bool MSRdone = true; + bool forcedUpdate = false; - // Determine error amount, include target, actual and dipper modifier - double controllerError = AutoTDPTargetFPS - processValueFPS - AutoTDPDipper(processValueFPS, AutoTDPTargetFPS); + // todo: Store fps for data gathering from multiple points (OSD, Performance) + double processValueFPS = PlatformManager.RTSS.GetFramerate(true); - // Clamp error amount corrected within a single cycle - // Adjust clamp if actual FPS is 2.5x requested FPS - double clampLowerLimit = processValueFPS >= 2.5 * AutoTDPTargetFPS ? -100 : -5; - controllerError = Math.Clamp(controllerError, clampLowerLimit, 15); + // Ensure realistic process values, prevent divide by 0 + processValueFPS = Math.Clamp(processValueFPS, 5, 500); - double TDPAdjustment = controllerError * AutoTDP / processValueFPS; - TDPAdjustment *= 0.9; // Always have a little undershoot + // Determine error amount, include target, actual and dipper modifier + double controllerError = AutoTDPTargetFPS - processValueFPS - AutoTDPDipper(processValueFPS, AutoTDPTargetFPS); - // Determine final setpoint - if (!AutoTDPFirstRun) - AutoTDP += TDPAdjustment + AutoTDPDamper(processValueFPS); - else - AutoTDPFirstRun = false; + // Clamp error amount corrected within a single cycle + // Adjust clamp if actual FPS is 2.5x requested FPS + double clampLowerLimit = processValueFPS >= 2.5 * AutoTDPTargetFPS ? -100 : -5; + controllerError = Math.Clamp(controllerError, clampLowerLimit, 15); - AutoTDP = Math.Clamp(AutoTDP, TDPMin, AutoTDPMax); + double TDPAdjustment = controllerError * AutoTDP / processValueFPS; + TDPAdjustment *= 0.9; // Always have a little undershoot - // Only update if we have a different TDP value to set - if (AutoTDP != AutoTDPPrev) - { - double[] values = new double[3] { AutoTDP, AutoTDP, AutoTDP }; - RequestTDP(values, true); - } - AutoTDPPrev = AutoTDP; + // Determine final setpoint + if (!AutoTDPFirstRun) + AutoTDP += TDPAdjustment + AutoTDPDamper(processValueFPS); + else + AutoTDPFirstRun = false; + + AutoTDP = Math.Clamp(AutoTDP, TDPMin, AutoTDPMax); - // LogManager.LogTrace("TDPSet;;;;;{0:0.0};{1:0.000};{2:0.0000};{3:0.0000};{4:0.0000}", AutoTDPTargetFPS, AutoTDP, TDPAdjustment, ProcessValueFPS, TDPDamping); + // LogManager.LogTrace("TDPSet;;;;;{0:0.0};{1:0.000};{2:0.0000};{3:0.0000};{4:0.0000}", AutoTDPTargetFPS, AutoTDP, TDPAdjustment, ProcessValueFPS, TDPDamping); + + // force update TDP periodically since we don't actually read current TDP + if (autotdpWatchdogCounter > COUNTER_AUTO) + { + forcedUpdate = true; + autotdpWatchdogCounter = 0; + } - // release lock - Exit: - autoLock.Exit(); + // Only update if we have a different TDP value to set + // or a forced update is requested + if (AutoTDP != AutoTDPPrev || forcedUpdate) + { + double[] values = new double[3] { AutoTDP, AutoTDP, AutoTDP }; + RequestTDP(values, true); + AutoTDPPrev = AutoTDP; + } + + // are we done ? + TDPdone = CurrentTDP[0] == StoredTDP[0] && CurrentTDP[1] == StoredTDP[1] && CurrentTDP[2] == StoredTDP[2]; + + // processor specific + if (processor is IntelProcessor) + { + double TDPslow = StoredTDP[(int)PowerType.Slow]; + double TDPfast = StoredTDP[(int)PowerType.Fast]; + + if (TDPslow != 0.0d && TDPfast != 0.0d) + // only request an update if current limit is different than stored + if (CurrentTDP[(int)PowerType.MsrSlow] != TDPslow || CurrentTDP[(int)PowerType.MsrFast] != TDPfast || forcedUpdate) + { + MSRdone = false; + RequestMSR(TDPslow, TDPfast); + } + } + + // user requested to halt AutoTDP watchdog + if (autotdpWatchdogPendingStop) + { + if (autotdpWatchdog.Interval == INTERVAL_AUTO) + { + if (TDPdone && MSRdone) + autotdpWatchdog.Stop(); + } + else if (autotdpWatchdog.Interval == INTERVAL_DEGRADED) + { + autotdpWatchdog.Stop(); + } + } + } + finally + { + // release lock + autotdpLock.Exit(); + } } } @@ -435,140 +515,126 @@ private static double AutoTDPDamper(double FPSActual) return TDPDamping; } - // todo: update this function to force (re)apply profile settings - private static void powerWatchdog_Elapsed(object? sender, ElapsedEventArgs e) + private static void cpuWatchdog_Elapsed(object? sender, ElapsedEventArgs e) { - if (powerLock.TryEnter()) + if (cpuLock.TryEnter()) { - // Checking if active power shceme has changed to reflect that - if (PowerGetEffectiveOverlayScheme(out Guid activeScheme) == 0) + try { - if (activeScheme != currentPowerMode) + if (currentProfile is not null) { - currentPowerMode = activeScheme; - int idx = Array.IndexOf(PowerModes, activeScheme); - if (idx != -1) - PowerModeChanged?.Invoke(idx); - } - } + // Check if CPU clock speed has changed and apply if needed + if (currentProfile.CPUOverrideEnabled) + RequestCPUClock(Convert.ToUInt32(currentProfile.CPUOverrideValue)); - // read perfboostmode - uint[] result = PowerScheme.ReadPowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PERFBOOSTMODE); - uint perfboostmode = result[(int)PowerIndexType.AC]; - if (perfboostmode != currentPerfBoostMode) - { - currentPerfBoostMode = perfboostmode; - PerfBoostModeChanged?.Invoke(perfboostmode); - } + // Check if CPU core count has changed and apply if needed + if (currentProfile.CPUCoreEnabled) + RequestCPUCoreCount(currentProfile.CPUCoreCount); - // Checking if current EPP value has changed to reflect that - uint[] EPP = PowerScheme.ReadPowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PERFEPP); - uint DCvalue = EPP[(int)PowerIndexType.DC]; + // Check if current EPP value has changed and apply if needed + if (currentProfile.EPPOverrideEnabled) + RequestEPP(currentProfile.EPPOverrideValue); - if (DCvalue != currentEPP) + // Check if active power shceme has changed and apply if needed + RequestPowerMode(currentProfile.OSPowerMode); + + // Check if PerfBoostMode value has changed and apply if needed + RequestPerfBoostMode((uint)currentProfile.CPUBoostLevel); + } + } + finally { - currentEPP = DCvalue; - EPPChanged?.Invoke(DCvalue); + // release lock + cpuLock.Exit(); } - - // release lock - Exit: - powerLock.Exit(); } } - private static async void cpuWatchdog_Elapsed(object? sender, ElapsedEventArgs e) + private static async void tdpWatchdog_Elapsed(object? sender, ElapsedEventArgs e) { if (processor is null || !processor.IsInitialized) return; - if (cpuLock.TryEnter()) + if (tdpLock.TryEnter()) { - bool TDPdone = false; - bool MSRdone = false; - - // read current values and (re)apply requested TDP if needed - foreach (PowerType type in (PowerType[])Enum.GetValues(typeof(PowerType))) + try { - var idx = (int)type; - - // skip msr - if (idx >= StoredTDP.Length) - break; + tdpWatchdogCounter++; - double TDP = StoredTDP[idx]; - if (TDP == 0.0d) - continue; + bool TDPdone = false; + bool MSRdone = true; + bool forcedUpdate = false; - if (processor is AMDProcessor) + // force update TDP periodically since we don't actually read current TDP + if (tdpWatchdogCounter > COUNTER_DEFAULT) { - // AMD reduces TDP by 10% when OS power mode is set to Best power efficiency - if (currentPowerMode == OSPowerMode.BetterBattery) - TDP = (int)Math.Truncate(TDP * 0.9); - - // AMD doesn't have MSR - if (type == PowerType.MsrSlow || type == PowerType.MsrFast) - continue; + forcedUpdate = true; + tdpWatchdogCounter = 0; } - else if (processor is IntelProcessor) - { - // Intel doesn't have stapm - if (type == PowerType.Stapm) - continue; - } - - // todo: find a way to read TDP limits - double ReadTDP = CurrentTDP[idx]; - if (ReadTDP != 0) - cpuWatchdog.Interval = INTERVAL_DEFAULT; - else - cpuWatchdog.Interval = INTERVAL_DEGRADED; - // only request an update if current limit is different than stored - if (ReadTDP != TDP) + // read current values and (re)apply requested TDP if needed + for (int idx = (int)PowerType.Slow; idx <= (int)PowerType.Fast; idx++) { - processor.SetTDPLimit(type, TDP); + double TDP = StoredTDP[idx]; + if (TDP == 0.0d) + continue; - // update TDP limits (temporary) - CurrentTDP[idx] = TDP; - } + // AMD reduces TDP by 10% when OS power mode is set to Best power efficiency + if (processor is AMDProcessor && currentPowerMode == OSPowerMode.BetterBattery) + TDP = (int)Math.Truncate(TDP * 0.9); - await Task.Delay(20); - } + // todo: find a way to read TDP limits + double ReadTDP = CurrentTDP[idx]; + if (ReadTDP != 0) + tdpWatchdog.Interval = INTERVAL_DEFAULT; + else + tdpWatchdog.Interval = INTERVAL_DEGRADED; - // are we done ? - TDPdone = CurrentTDP[0] == StoredTDP[0] && CurrentTDP[1] == StoredTDP[1] && CurrentTDP[2] == StoredTDP[2]; + // only request an update if current limit is different than stored + // or a forced update is requested + if (ReadTDP != TDP || forcedUpdate) + RequestTDP((PowerType)idx, TDP, true); - // processor specific - if (processor is IntelProcessor) - { - int TDPslow = (int)StoredTDP[(int)PowerType.Slow]; - int TDPfast = (int)StoredTDP[(int)PowerType.Fast]; + await Task.Delay(20); + } - // only request an update if current limit is different than stored - if (CurrentTDP[(int)PowerType.MsrSlow] != TDPslow || CurrentTDP[(int)PowerType.MsrFast] != TDPfast) - ((IntelProcessor)processor).SetMSRLimit(TDPslow, TDPfast); - else - MSRdone = true; - } + // are we done ? + TDPdone = CurrentTDP[0] == StoredTDP[0] && CurrentTDP[1] == StoredTDP[1] && CurrentTDP[2] == StoredTDP[2]; - // user requested to halt cpu watchdog - if (cpuWatchdogPendingStop) - { - if (cpuWatchdog.Interval == INTERVAL_DEFAULT) + // processor specific + if (processor is IntelProcessor) { - if (TDPdone && MSRdone) - cpuWatchdog.Stop(); + double TDPslow = StoredTDP[(int)PowerType.Slow]; + double TDPfast = StoredTDP[(int)PowerType.Fast]; + + if (TDPslow != 0.0d && TDPfast != 0.0d) + // only request an update if current limit is different than stored + if (CurrentTDP[(int)PowerType.MsrSlow] != TDPslow || CurrentTDP[(int)PowerType.MsrFast] != TDPfast || forcedUpdate) + { + MSRdone = false; + RequestMSR(TDPslow, TDPfast); + } } - else if (cpuWatchdog.Interval == INTERVAL_DEGRADED) + + // user requested to halt TDP watchdog + if (tdpWatchdogPendingStop) { - cpuWatchdog.Stop(); + if (tdpWatchdog.Interval == INTERVAL_DEFAULT) + { + if (TDPdone && MSRdone) + tdpWatchdog.Stop(); + } + else if (tdpWatchdog.Interval == INTERVAL_DEGRADED) + { + tdpWatchdog.Stop(); + } } } - - // release lock - Exit: - cpuLock.Exit(); + finally + { + // release lock + tdpLock.Exit(); + } } } @@ -577,61 +643,67 @@ private static void gfxWatchdog_Elapsed(object? sender, ElapsedEventArgs e) if (processor is null || !processor.IsInitialized) return; + GPU GPU = GPUManager.GetCurrent(); + if (GPU is null || !GPU.IsInitialized) + return; + if (gfxLock.TryEnter()) { - bool GPUdone = false; - GPU GPU = GPUManager.GetCurrent(); - if (GPU is null) + try { - // release lock - goto Exit; - } + gfxWatchdogCounter++; - float CurrentGfxClock = GPUManager.GetCurrent().GetClock(); + bool GPUdone = true; + bool forcedUpdate = false; - if (CurrentGfxClock != 0) - gfxWatchdog.Interval = INTERVAL_DEFAULT; - else - gfxWatchdog.Interval = INTERVAL_DEGRADED; + // not ready yet + if (StoredGfxClock == 0) + return; - // not ready yet - if (StoredGfxClock == 0) - { - // release lock - goto Exit; - } + float CurrentGfxClock = GPUManager.GetCurrent().GetClock(); - // only request an update if current gfx clock is different than stored - if (CurrentGfxClock != StoredGfxClock) - { - // disabling - if (StoredGfxClock == 12750) - GPUdone = true; + if (CurrentGfxClock != 0) + gfxWatchdog.Interval = INTERVAL_DEFAULT; else - processor.SetGPUClock(StoredGfxClock); - } - else - { - GPUdone = true; - } + gfxWatchdog.Interval = INTERVAL_DEGRADED; - // user requested to halt gpu watchdog - if (gfxWatchdogPendingStop) - { - if (gfxWatchdog.Interval == INTERVAL_DEFAULT) + if (gfxWatchdogCounter > COUNTER_DEFAULT) { - if (GPUdone) - gfxWatchdog.Stop(); + forcedUpdate = true; + gfxWatchdogCounter = 0; + } + + // only request an update if current gfx clock is different than stored + // or a forced update is requested + if (CurrentGfxClock != StoredGfxClock || forcedUpdate) + { + // disabling + if (StoredGfxClock != 12750) + { + GPUdone = false; + RequestGPUClock(StoredGfxClock, true); + } } - else if (gfxWatchdog.Interval == INTERVAL_DEGRADED) + + // user requested to halt gpu watchdog + if (gfxWatchdogPendingStop) { - gfxWatchdog.Stop(); + if (gfxWatchdog.Interval == INTERVAL_DEFAULT) + { + if (GPUdone) + gfxWatchdog.Stop(); + } + else if (gfxWatchdog.Interval == INTERVAL_DEGRADED) + { + gfxWatchdog.Stop(); + } } } - - // release lock - Exit: - gfxLock.Exit(); + finally + { + // release lock + gfxLock.Exit(); + } } } @@ -651,33 +723,34 @@ private static void StopGPUWatchdog(bool immediate = false) private static void StartTDPWatchdog() { - cpuWatchdogPendingStop = false; - cpuWatchdog.Interval = INTERVAL_DEFAULT; - cpuWatchdog.Start(); + tdpWatchdogPendingStop = false; + tdpWatchdog.Interval = INTERVAL_DEFAULT; + tdpWatchdog.Start(); } private static void StopTDPWatchdog(bool immediate = false) { - cpuWatchdogPendingStop = true; + tdpWatchdogPendingStop = true; if (immediate) - cpuWatchdog.Stop(); + tdpWatchdog.Stop(); } private static void StartAutoTDPWatchdog() { - autoWatchdog.Start(); + autotdpWatchdogPendingStop = false; + autotdpWatchdog.Interval = INTERVAL_AUTO; + autotdpWatchdog.Start(); } private static void StopAutoTDPWatchdog(bool immediate = false) { - autoWatchdog.Stop(); + autotdpWatchdogPendingStop = true; + if (immediate) + autotdpWatchdog.Stop(); } private static void RequestTDP(PowerType type, double value, bool immediate = false) { - if (processor is null || !processor.IsInitialized) - return; - // make sure we're not trying to run below or above specs value = Math.Min(TDPMax, Math.Max(TDPMin, value)); @@ -685,11 +758,20 @@ private static void RequestTDP(PowerType type, double value, bool immediate = fa int idx = (int)type; StoredTDP[idx] = value; + if (processor is null || !processor.IsInitialized) + return; + // immediately apply if (immediate) { - processor.SetTDPLimit((PowerType)idx, value, immediate); CurrentTDP[idx] = value; + + if (processor is IntelProcessor) + // Intel doesn't have stapm + if (type == PowerType.Stapm) + return; + + processor.SetTDPLimit((PowerType)idx, value, immediate); } } @@ -706,32 +788,64 @@ private static async void RequestTDP(double[] values, bool immediate = false) } } - private static void RequestGPUClock(double value, bool immediate = false) + private static void RequestMSR(double PL1, double PL2) { if (processor is null || !processor.IsInitialized) return; + if (processor is IntelProcessor) + { + // make sure we're not trying to run below or above specs + double TDPslow = Math.Min(TDPMax, Math.Max(TDPMin, PL1)); + double TDPfast = Math.Min(TDPMax, Math.Max(TDPMin, PL2)); + + CurrentTDP[(int)PowerType.MsrSlow] = TDPslow; + CurrentTDP[(int)PowerType.MsrFast] = TDPfast; + ((IntelProcessor)processor).SetMSRLimit(TDPslow, TDPfast); + } + } + + private static void RequestGPUClock(double value, bool immediate = false) + { // update value read by timer StoredGfxClock = value; + if (processor is null || !processor.IsInitialized) + return; + // immediately apply if (immediate) - processor.SetGPUClock(value); + { + int result = 0; + processor.SetGPUClock(StoredGfxClock, ref result); + + if (result != 0) + LogManager.LogWarning("Failed to set requested GPU clock: {0}, error code: {1}", StoredGfxClock, result); + } } private static void RequestPowerMode(Guid guid) { currentPowerMode = guid; + + if (PowerGetEffectiveOverlayScheme(out Guid activeScheme) == 0) + if (activeScheme == currentPowerMode) + return; + LogManager.LogDebug("User requested power scheme: {0}", currentPowerMode); if (PowerSetActiveOverlayScheme(currentPowerMode) != 0) LogManager.LogWarning("Failed to set requested power scheme: {0}", currentPowerMode); + else + { + int idx = Array.IndexOf(PowerModes, currentPowerMode); + if (idx != -1) + PowerModeChanged?.Invoke(idx); + } } private static void RequestEPP(uint EPPOverrideValue) { - currentEPP = EPPOverrideValue; - var requestedEPP = new uint[2] { (uint)Math.Max(0, (int)EPPOverrideValue - 17), @@ -753,12 +867,12 @@ private static void RequestEPP(uint EPPOverrideValue) EPP = PowerScheme.ReadPowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PERFEPP); if (EPP[0] != requestedEPP[0] || EPP[1] != requestedEPP[1]) LogManager.LogWarning("Failed to set requested EPP"); + else + EPPChanged?.Invoke(EPPOverrideValue); } private static void RequestCPUCoreCount(int CoreCount) { - currentCoreCount = CoreCount; - uint currentCoreCountPercent = (uint)((100.0d / MotherboardInfo.NumberOfCores) * CoreCount); // Is the CPMINCORES value already correct? @@ -791,17 +905,26 @@ private static void RequestCPUCoreCount(int CoreCount) private static void RequestPerfBoostMode(uint value) { - currentPerfBoostMode = value; + // Is the PerfBoostMode value already correct? + uint[] perfBoostMode = PowerScheme.ReadPowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PERFBOOSTMODE); + bool IsReady = (perfBoostMode[0] == value && perfBoostMode[1] == value); + if (IsReady) + return; + + // Set profile PerfBoostMode PowerScheme.WritePowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PERFBOOSTMODE, value, value); LogManager.LogDebug("User requested perfboostmode: {0}", value); + + // Has the value been applied? + perfBoostMode = PowerScheme.ReadPowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PERFBOOSTMODE); + if (perfBoostMode[0] != value || perfBoostMode[1] != value) + LogManager.LogWarning("Failed to set requested perfboostmode"); } private static void RequestCPUClock(uint cpuClock) { - double maxClock = MotherboardInfo.ProcessorMaxTurboSpeed; - // Is the PROCFREQMAX value already correct? uint[] currentClock = PowerScheme.ReadPowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PROCFREQMAX); bool IsReady = (currentClock[0] == cpuClock && currentClock[1] == cpuClock); @@ -809,8 +932,11 @@ private static void RequestCPUClock(uint cpuClock) if (IsReady) return; + // Set profile max processor frequency PowerScheme.WritePowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PROCFREQMAX, cpuClock, cpuClock); + PowerScheme.WritePowerCfg(PowerSubGroup.SUB_PROCESSOR, PowerSetting.PROCFREQMAX1, cpuClock, cpuClock); + double maxClock = MotherboardInfo.ProcessorMaxTurboSpeed; double cpuPercentage = cpuClock / maxClock * 100.0d; LogManager.LogDebug("User requested PROCFREQMAX: {0} ({1}%)", cpuClock, cpuPercentage); @@ -823,7 +949,7 @@ private static void RequestCPUClock(uint cpuClock) public static void Start() { // initialize watchdog(s) - powerWatchdog.Start(); + cpuWatchdog.Start(); // initialize processor processor = Processor.GetCurrent(); @@ -852,14 +978,14 @@ public static void Stop() if (processor is not null && processor.IsInitialized) processor.Stop(); - powerWatchdog.Stop(); - cpuWatchdog.Stop(); + autotdpWatchdog.Stop(); + tdpWatchdog.Stop(); gfxWatchdog.Stop(); - autoWatchdog.Stop(); + cpuWatchdog.Stop(); IsInitialized = false; - LogManager.LogInformation("{0} has started", "PerformanceManager"); + LogManager.LogInformation("{0} has stopped", "PerformanceManager"); } public static void Resume(bool OS) diff --git a/HandheldCompanion/Managers/PlatformManager.cs b/HandheldCompanion/Managers/PlatformManager.cs index 96416fd9e..b934a5c02 100644 --- a/HandheldCompanion/Managers/PlatformManager.cs +++ b/HandheldCompanion/Managers/PlatformManager.cs @@ -9,8 +9,6 @@ namespace HandheldCompanion.Managers; public static class PlatformManager { - private const int UpdateInterval = 1000; - // gaming platforms public static readonly Steam Steam = new(); public static readonly GOGGalaxy GOGGalaxy = new(); @@ -20,7 +18,8 @@ public static class PlatformManager public static RTSS RTSS = new(); public static Platforms.LibreHardwareMonitor LibreHardwareMonitor = new(); - private static Timer UpdateTimer; + private const int UpdateInterval = 1000; + private static Timer UpdateTimer = new() { Interval = UpdateInterval, AutoReset = false }; private static bool IsInitialized; @@ -52,18 +51,14 @@ public static void Start() if (LibreHardwareMonitor.IsInstalled) LibreHardwareMonitor.Start(); + + UpdateTimer.Elapsed += (sender, e) => MonitorPlatforms(); + UpdateTimer.Start(); SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; ProfileManager.Applied += ProfileManager_Applied; PowerProfileManager.Applied += PowerProfileManager_Applied; - UpdateTimer = new Timer(UpdateInterval) - { - AutoReset = false - }; - UpdateTimer.Elapsed += (sender, e) => MonitorPlatforms(); - UpdateTimer.Start(); - IsInitialized = true; Initialized?.Invoke(); @@ -94,7 +89,7 @@ private static void ProfileManager_Applied(Profile profile, UpdateSource source) UpdateTimer.Start(); } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { // UI thread Application.Current.Dispatcher.Invoke(() => @@ -206,6 +201,10 @@ public static void Stop() if (LibreHardwareMonitor.IsInstalled) LibreHardwareMonitor.Stop(); + SettingsManager.SettingValueChanged -= SettingsManager_SettingValueChanged; + ProfileManager.Applied -= ProfileManager_Applied; + PowerProfileManager.Applied -= PowerProfileManager_Applied; + IsInitialized = false; LogManager.LogInformation("{0} has stopped", "PlatformManager"); diff --git a/HandheldCompanion/Managers/PowerProfileManager.cs b/HandheldCompanion/Managers/PowerProfileManager.cs index f235f9fe7..a44a12399 100644 --- a/HandheldCompanion/Managers/PowerProfileManager.cs +++ b/HandheldCompanion/Managers/PowerProfileManager.cs @@ -206,13 +206,13 @@ public static PowerProfile GetProfile(Guid guid) private static bool HasDefault() { - return profiles.Values.Count(a => a.Default) != 0; + return profiles.Values.Count(a => a.Default && a.Guid == Guid.Empty) != 0; } public static PowerProfile GetDefault() { if (HasDefault()) - return profiles.Values.FirstOrDefault(a => a.Default); + return profiles.Values.FirstOrDefault(a => a.Default && a.Guid == Guid.Empty); return new PowerProfile(); } diff --git a/HandheldCompanion/Managers/SensorsManager.cs b/HandheldCompanion/Managers/SensorsManager.cs index 9ccfa7515..da49ea7f5 100644 --- a/HandheldCompanion/Managers/SensorsManager.cs +++ b/HandheldCompanion/Managers/SensorsManager.cs @@ -6,6 +6,7 @@ using HandheldCompanion.Views; using Nefarius.Utilities.DeviceManagement.PnP; using System; +using System.Collections.Generic; using System.Numerics; using System.Threading.Tasks; using static HandheldCompanion.Utils.DeviceUtils; @@ -95,7 +96,7 @@ private static void DeviceManager_UsbDeviceArrived(PnPDevice device, DeviceEvent SettingsManager.SetProperty("SensorSelection", (int)SensorFamily.SerialUSBIMU); } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { @@ -244,6 +245,11 @@ public static void SetSensorFamily(SensorFamily sensorFamily) } public static async void Calibrate(GamepadMotion gamepadMotion) + { + Calibrate(new Dictionary { { 0, gamepadMotion } }); + } + + public static async void Calibrate(Dictionary gamepadMotions) { Dialog dialog = new Dialog(MainWindow.GetCurrent()) { @@ -255,8 +261,8 @@ public static async void Calibrate(GamepadMotion gamepadMotion) // display calibration dialog dialog.Show(); - // skip if null - if (gamepadMotion is null) + // skip if empty + if (gamepadMotions.Count == 0) goto Close; for (int i = 4; i > 0; i--) @@ -265,83 +271,91 @@ public static async void Calibrate(GamepadMotion gamepadMotion) await Task.Delay(1000); } - dialog.UpdateContent("Calibrating stationary sensor noise and drift correction..."); + foreach (GamepadMotion gamepadMotion in gamepadMotions.Values) + { + dialog.UpdateContent($"Calibrating {gamepadMotion.deviceInstanceId} stationary sensor noise and drift correction..."); - // reset motion values - gamepadMotion.ResetMotion(); + // reset motion values + gamepadMotion.ResetMotion(); - // wait until device is steady - DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); - while (DateTime.Now < timeout && !gamepadMotion.GetAutoCalibrationIsSteady()) - await Task.Delay(100); + // wait until device is steady + DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); + while (DateTime.Now < timeout && !gamepadMotion.GetAutoCalibrationIsSteady()) + await Task.Delay(100); - // device is either too shaky or stalled - bool IsSteady = gamepadMotion.GetAutoCalibrationIsSteady(); - if (!IsSteady) - { - gamepadMotion.GetCalibratedGyro(out float x, out float y, out float z); + // device is either too shaky or stalled + bool IsSteady = gamepadMotion.GetAutoCalibrationIsSteady(); + if (!IsSteady) + { + gamepadMotion.GetCalibratedGyro(out float x, out float y, out float z); - // display message - if (x == 0 && y == 0 && z == 0) - dialog.UpdateContent("Calibration failed: device is silent."); - else - dialog.UpdateContent("Calibration failed: device is silent or unsteady."); + // display message + if (x == 0 && y == 0 && z == 0) + dialog.UpdateContent($"Calibration failed: device is silent."); + else + dialog.UpdateContent($"Calibration device is silent or unsteady."); - goto Close; - } + // wait a bit + await Task.Delay(2000); - // start continuous calibration - gamepadMotion.StartContinuousCalibration(); + break; + } - // give gamepad motion 3 seconds to get values - timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); - while (DateTime.Now < timeout) - await Task.Delay(100); + // start continuous calibration + gamepadMotion.StartContinuousCalibration(); - // halt continuous calibration - gamepadMotion.PauseContinuousCalibration(); + // give gamepad motion 3 seconds to get values + timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); + while (DateTime.Now < timeout) + await Task.Delay(100); - // get continuous calibration confidence - float confidence = gamepadMotion.GetAutoCalibrationConfidence(); + // halt continuous calibration + gamepadMotion.PauseContinuousCalibration(); - // get/set calibration offsets - gamepadMotion.GetCalibrationOffset(out float xOffset, out float yOffset, out float zOffset); - gamepadMotion.SetCalibrationOffset(xOffset, yOffset, zOffset, (int)(confidence * 10.0f)); + // get continuous calibration confidence + float confidence = gamepadMotion.GetAutoCalibrationConfidence(); - /* - dialog.UpdateTitle("Please take back the controller in hands and get ready to shake it."); + // get/set calibration offsets + gamepadMotion.GetCalibrationOffset(out float xOffset, out float yOffset, out float zOffset); + gamepadMotion.SetCalibrationOffset(xOffset, yOffset, zOffset, (int)(confidence * 10.0f)); - for (int i = 4; i > 0; i--) - { - dialog.UpdateContent($"Threshold calibration will start in {i} seconds."); - await Task.Delay(1000); - } + /* + dialog.UpdateTitle("Please take back the controller in hands and get ready to shake it."); - dialog.UpdateContent("Shake the device in all direction..."); + for (int i = 4; i > 0; i--) + { + dialog.UpdateContent($"Threshold calibration will start in {i} seconds."); + await Task.Delay(1000); + } - // reset motion values - gamepadMotion.ResetThresholdCalibration(); - gamepadMotion.StartThresholdCalibration(); + dialog.UpdateContent("Shake the device in all direction..."); - // wait until device is steady - timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); - while (DateTime.Now < timeout) - await Task.Delay(100); + // reset motion values + gamepadMotion.ResetThresholdCalibration(); + gamepadMotion.StartThresholdCalibration(); - gamepadMotion.PauseThresholdCalibration(); + // wait until device is steady + timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); + while (DateTime.Now < timeout) + await Task.Delay(100); - // get calibration offsets - gamepadMotion.SetCalibrationThreshold(gamepadMotion.maxGyro, gamepadMotion.maxAccel); - */ + gamepadMotion.PauseThresholdCalibration(); - // store calibration offsets - IMUCalibration.StoreCalibration(gamepadMotion.deviceInstanceId, gamepadMotion.GetCalibration()); + // get calibration offsets + gamepadMotion.SetCalibrationThreshold(gamepadMotion.maxGyro, gamepadMotion.maxAccel); + */ - // display message - dialog.UpdateContent($"Calibration succeeded: stationary sensor noise recorded. Drift correction found. Confidence: {confidence * 100.0f}%"); + // store calibration offsets + IMUCalibration.StoreCalibration(gamepadMotion.deviceInstanceId, gamepadMotion.GetCalibration()); + + // display message + dialog.UpdateContent($"Calibration succeeded: stationary sensor noise recorded. Drift correction found. Confidence: {confidence * 100.0f}%"); + + // wait a bit + await Task.Delay(2000); + } Close: - await Task.Delay(2000); dialog.Hide(); } } diff --git a/HandheldCompanion/Managers/SettingsManager.cs b/HandheldCompanion/Managers/SettingsManager.cs index fa77eeaee..219f5fcc2 100644 --- a/HandheldCompanion/Managers/SettingsManager.cs +++ b/HandheldCompanion/Managers/SettingsManager.cs @@ -32,7 +32,7 @@ public static class SettingsManager { public delegate void InitializedEventHandler(); - public delegate void SettingValueChangedEventHandler(string name, object value); + public delegate void SettingValueChangedEventHandler(string name, object value, bool temporary); private static readonly Dictionary Settings = []; @@ -55,7 +55,7 @@ public static void Start() .OrderBy(s => s.Name); foreach (var property in properties) - SettingValueChanged(property.Name, GetProperty(property.Name)); + SettingValueChanged(property.Name, GetProperty(property.Name), false); if (GetBoolean("FirstStart")) SetProperty("FirstStart", false); @@ -117,7 +117,7 @@ public static void SetProperty(string name, object value, bool force = false, bo Settings[name] = value; // raise event - SettingValueChanged?.Invoke(name, value); + SettingValueChanged?.Invoke(name, value, temporary); LogManager.LogDebug("Settings {0} set to {1}", name, value); } diff --git a/HandheldCompanion/Managers/SystemManager.cs b/HandheldCompanion/Managers/SystemManager.cs index 02a76d09c..823702665 100644 --- a/HandheldCompanion/Managers/SystemManager.cs +++ b/HandheldCompanion/Managers/SystemManager.cs @@ -9,10 +9,28 @@ namespace HandheldCompanion.Managers; public static class SystemManager { - // Import SetThreadExecutionState Win32 API and define flags + #region PInvoke + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint SetThreadExecutionState(uint esFlags); + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess); + + #endregion + + #region Events + + public static event SystemStatusChangedEventHandler SystemStatusChanged; + public static event PowerStatusChangedEventHandler PowerStatusChanged; + public static event InitializedEventHandler Initialized; + + public delegate void SystemStatusChangedEventHandler(SystemStatus status, SystemStatus prevStatus); + public delegate void PowerStatusChangedEventHandler(PowerStatus status); + public delegate void InitializedEventHandler(); + + #endregion + public const uint ES_CONTINUOUS = 0x80000000; public const uint ES_SYSTEM_REQUIRED = 0x00000001; @@ -73,6 +91,11 @@ public enum SystemStatus static SystemManager() { // listen to system events + SubscribeToSystemEvents(); + } + + private static void SubscribeToSystemEvents() + { SystemEvents.PowerModeChanged += OnPowerChange; SystemEvents.SessionSwitch += OnSessionSwitch; @@ -83,12 +106,17 @@ static SystemManager() SystemPowerManager.RemainingDischargeTimeChanged += BatteryStatusChanged; } - #region import - - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess); + private static void UnsubscribeFromSystemEvents() + { + SystemEvents.PowerModeChanged -= OnPowerChange; + SystemEvents.SessionSwitch -= OnSessionSwitch; - #endregion + SystemPowerManager.BatteryStatusChanged -= BatteryStatusChanged; + SystemPowerManager.EnergySaverStatusChanged -= BatteryStatusChanged; + SystemPowerManager.PowerSupplyStatusChanged -= BatteryStatusChanged; + SystemPowerManager.RemainingChargePercentChanged -= BatteryStatusChanged; + SystemPowerManager.RemainingDischargeTimeChanged -= BatteryStatusChanged; + } private static void BatteryStatusChanged(object sender, object e) { @@ -97,11 +125,13 @@ private static void BatteryStatusChanged(object sender, object e) public static void Start() { - // check if current session is locked - var handle = OpenInputDesktop(0, false, 0); - IsSessionLocked = handle == IntPtr.Zero; + if (IsInitialized) + return; - SystemRoutine(); + // Check if current session is locked + IsSessionLocked = OpenInputDesktop(0, false, 0) == IntPtr.Zero; + + PerformSystemRoutine(); IsInitialized = true; Initialized?.Invoke(); @@ -119,8 +149,7 @@ public static void Stop() IsInitialized = false; // stop listening to system events - SystemEvents.PowerModeChanged -= OnPowerChange; - SystemEvents.SessionSwitch -= OnSessionSwitch; + UnsubscribeFromSystemEvents(); LogManager.LogInformation("{0} has stopped", "PowerManager"); } @@ -132,13 +161,16 @@ private static void OnPowerChange(object s, PowerModeChangedEventArgs e) case PowerModes.Resume: IsPowerSuspended = false; break; + case PowerModes.Suspend: IsPowerSuspended = true; // Prevent system sleep SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); + LogManager.LogDebug("System is trying to suspend. Performing tasks..."); break; + default: case PowerModes.StatusChange: PowerStatusChanged?.Invoke(SystemInformation.PowerStatus); @@ -147,7 +179,7 @@ private static void OnPowerChange(object s, PowerModeChangedEventArgs e) LogManager.LogDebug("Device power mode set to {0}", e.Mode); - SystemRoutine(); + PerformSystemRoutine(); } private static void OnSessionSwitch(object sender, SessionSwitchEventArgs e) @@ -166,10 +198,10 @@ private static void OnSessionSwitch(object sender, SessionSwitchEventArgs e) LogManager.LogDebug("Session switched to {0}", e.Reason); - SystemRoutine(); + PerformSystemRoutine(); } - private static void SystemRoutine() + private static void PerformSystemRoutine() { if (!IsPowerSuspended && !IsSessionLocked) currentSystemStatus = SystemStatus.SystemReady; @@ -177,28 +209,12 @@ private static void SystemRoutine() currentSystemStatus = SystemStatus.SystemPending; // only raise event is system status has changed - if (previousSystemStatus != currentSystemStatus) - { - LogManager.LogInformation("System status set to {0}", currentSystemStatus); - SystemStatusChanged?.Invoke(currentSystemStatus, previousSystemStatus); - - previousSystemStatus = currentSystemStatus; - } - } - - #region events - - public static event SystemStatusChangedEventHandler SystemStatusChanged; - - public delegate void SystemStatusChangedEventHandler(SystemStatus status, SystemStatus prevStatus); - - public static event PowerStatusChangedEventHandler PowerStatusChanged; - - public delegate void PowerStatusChangedEventHandler(PowerStatus status); - - public static event InitializedEventHandler Initialized; + if (previousSystemStatus == currentSystemStatus) + return; - public delegate void InitializedEventHandler(); + LogManager.LogInformation("System status set to {0}", currentSystemStatus); + SystemStatusChanged?.Invoke(currentSystemStatus, previousSystemStatus); - #endregion + previousSystemStatus = currentSystemStatus; + } } \ No newline at end of file diff --git a/HandheldCompanion/Managers/TaskManager.cs b/HandheldCompanion/Managers/TaskManager.cs index f3a06d8ad..14cbb1ed1 100644 --- a/HandheldCompanion/Managers/TaskManager.cs +++ b/HandheldCompanion/Managers/TaskManager.cs @@ -24,7 +24,7 @@ static TaskManager() SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Managers/UpdateManager.cs b/HandheldCompanion/Managers/UpdateManager.cs index 341d192ba..37217db07 100644 --- a/HandheldCompanion/Managers/UpdateManager.cs +++ b/HandheldCompanion/Managers/UpdateManager.cs @@ -74,7 +74,7 @@ static UpdateManager() SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Managers/VirtualManager.cs b/HandheldCompanion/Managers/VirtualManager.cs index 4f383b3a1..fe701cd52 100644 --- a/HandheldCompanion/Managers/VirtualManager.cs +++ b/HandheldCompanion/Managers/VirtualManager.cs @@ -18,8 +18,6 @@ public static class VirtualManager public static ViGEmClient vClient; public static ViGEmTarget vTarget; - private static DSUServer DSUServer; - // settings vars public static HIDmode HIDmode = HIDmode.NoController; private static HIDmode defaultHIDmode = HIDmode.NoController; @@ -59,9 +57,6 @@ static VirtualManager() MessageBox.Show("Unable to start Handheld Companion, the ViGEm application is missing.\n\nPlease get it from: https://github.com/ViGEm/ViGEmBus/releases", "Error"); throw new InvalidOperationException(); } - - // initialize DSUClient - DSUServer = new DSUServer(); } public static void Start() @@ -139,7 +134,7 @@ public static void Suspend(bool OS) } } - private static void SettingsManager_SettingValueChanged(string name, object value) + private static void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { @@ -322,11 +317,7 @@ private static void OnTargetVibrated(byte LargeMotor, byte SmallMotor) public static void UpdateInputs(ControllerState controllerState, GamepadMotion gamepadMotion) { - // DS4Touch is used by both targets below, update first - DS4Touch.UpdateInputs(controllerState); - vTarget?.UpdateInputs(controllerState, gamepadMotion); - DSUServer?.UpdateInputs(controllerState); } } } \ No newline at end of file diff --git a/HandheldCompanion/Misc/Hotkey.cs b/HandheldCompanion/Misc/Hotkey.cs index 4f05196b7..97b44ecc0 100644 --- a/HandheldCompanion/Misc/Hotkey.cs +++ b/HandheldCompanion/Misc/Hotkey.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Data; using System.Linq; using WindowsInput.Events; @@ -58,5 +59,14 @@ public object Clone() return hotkey; } + + public void Execute(bool onKeyDown, bool onKeyUp, bool IsBackground) + { + bool Rumble = SettingsManager.GetBoolean("HotkeyRumbleOnExecution"); + if (Rumble && !IsBackground && !IsInternal) + ControllerManager.GetTargetController()?.Rumble(); + + command?.Execute(command.OnKeyDown, command.OnKeyUp, IsBackground); + } } } diff --git a/HandheldCompanion/Misc/JoyShockLibrary.cs b/HandheldCompanion/Misc/JoyShockLibrary.cs index 2895233b6..045894eb9 100644 --- a/HandheldCompanion/Misc/JoyShockLibrary.cs +++ b/HandheldCompanion/Misc/JoyShockLibrary.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +using HandheldCompanion.Managers; +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; public static class JSL { @@ -98,8 +101,20 @@ public delegate void EventCallback(int handle, JOY_SHOCK_STATE state, JOY_SHOCK_ public static extern int JslConnectDevices(); [DllImport("JoyShockLibrary")] public static extern int JslGetConnectedDeviceHandles(int[] deviceHandleArray, int size); + [DllImport("JoyShockLibrary")] public static extern void JslDisconnectAndDisposeAll(); + + public static void JslDisconnect() + { + // Flushing possible JoyShocks... + Task jslTask = Task.Run(() => JslDisconnectAndDisposeAll()); + + bool completedInTime = jslTask.Wait(TimeSpan.FromSeconds(2)); + if (!completedInTime) + LogManager.LogWarning("JslDisconnectAndDisposeAll() timed out."); + } + [DllImport("JoyShockLibrary")] public static extern bool JslStillConnected(int deviceId); [DllImport("JoyShockLibrary")] diff --git a/HandheldCompanion/Misc/PnPDetails.cs b/HandheldCompanion/Misc/PnPDetails.cs index 5f69b902a..287d3c85c 100644 --- a/HandheldCompanion/Misc/PnPDetails.cs +++ b/HandheldCompanion/Misc/PnPDetails.cs @@ -20,6 +20,7 @@ public class PnPDetails public bool isPhysical => !isVirtual; public bool isBluetooth => EnumeratorName.Equals("BTHENUM"); public bool isUSB => EnumeratorName.Equals("USB"); + public bool isDongle = false; public string devicePath = string.Empty; public string baseContainerDevicePath = string.Empty; diff --git a/HandheldCompanion/Misc/PowerProfile.cs b/HandheldCompanion/Misc/PowerProfile.cs index 8f10745b0..fdc5aa1c1 100644 --- a/HandheldCompanion/Misc/PowerProfile.cs +++ b/HandheldCompanion/Misc/PowerProfile.cs @@ -66,7 +66,7 @@ public string GetFileName() public bool IsDefault() { - return Default; + return Default && Guid == Guid.Empty; } public override string ToString() diff --git a/HandheldCompanion/Misc/PowerScheme.cs b/HandheldCompanion/Misc/PowerScheme.cs index e55f42e63..be26c6bd1 100644 --- a/HandheldCompanion/Misc/PowerScheme.cs +++ b/HandheldCompanion/Misc/PowerScheme.cs @@ -25,6 +25,10 @@ public static Guid PROCFREQMAX = new("75b0ae3f-bce0-45a7-8c89-c9611c25e100"); // Maximum processor frequency in MHz, 0 for no limit (default) + public static Guid + PROCFREQMAX1 = + new("75b0ae3f-bce0-45a7-8c89-c9611c25e101"); // Maximum processor frequency for processor power efficiency class 1 in MHz, 0 for no limit (default) + public static Guid CPMINCORES = new("0cc5b647-c1df-4637-891a-dec35c318583"); // Processor performance core parking min cores, expressed as a percent from 0 - 100 @@ -121,9 +125,9 @@ public static bool SetValue(PowerIndexType powerType, Guid SchemeGuid, Guid SubG return false; } - public static bool SetAttribute(Guid SubGroupOfPowerSettingsGuid, Guid PowerSettingGuid, bool hide) + public static bool SetAttribute(Guid SubGroupOfPowerSettingsGuid, Guid PowerSettingGuid, uint value) { - return PowerWriteSettingAttributes(SubGroupOfPowerSettingsGuid, PowerSettingGuid, (uint)(hide ? 1 : 0)) == 0; + return PowerWriteSettingAttributes(SubGroupOfPowerSettingsGuid, PowerSettingGuid, value) == 0; } public static uint[] ReadPowerCfg(Guid SubGroup, Guid Settings) @@ -147,7 +151,7 @@ public static void WritePowerCfg(Guid SubGroup, Guid Settings, uint ACValue, uin if (GetActiveScheme(out var currentScheme)) { // unhide attribute - SetAttribute(SubGroup, Settings, false); + SetAttribute(SubGroup, Settings, 2); // set value(s) SetValue(PowerIndexType.AC, currentScheme, SubGroup, Settings, ACValue); diff --git a/HandheldCompanion/Misc/Profile.cs b/HandheldCompanion/Misc/Profile.cs index 6a44b1d13..3049dec62 100644 --- a/HandheldCompanion/Misc/Profile.cs +++ b/HandheldCompanion/Misc/Profile.cs @@ -49,19 +49,20 @@ public partial class Profile : ICloneable, IComparable public string Path { get; set; } = string.Empty; public string Arguments { get; set; } = string.Empty; - public bool IsSubProfile { get; set; } = false; - public bool IsFavoriteSubProfile { get; set; } = false; + public bool IsSubProfile { get; set; } + public bool IsFavoriteSubProfile { get; set; } public Guid Guid { get; set; } = Guid.NewGuid(); public string Executable { get; set; } = string.Empty; public bool Enabled { get; set; } + public bool IsPinned { get; set; } = true; public bool Default { get; set; } public Version Version { get; set; } = new(); public string LayoutTitle { get; set; } = string.Empty; - public bool LayoutEnabled { get; set; } = false; + public bool LayoutEnabled { get; set; } public Layout Layout { get; set; } = new(); public bool Whitelisted { get; set; } // if true, can see through the HidHide cloak @@ -101,16 +102,16 @@ public partial class Profile : ICloneable, IComparable }; public int FramerateValue { get; set; } = 0; // default RTSS value - public bool GPUScaling { get; set; } = false; + public bool GPUScaling { get; set; } public int ScalingMode { get; set; } = 0; // default AMD value - public bool RSREnabled { get; set; } = false; + public bool RSREnabled { get; set; } public int RSRSharpness { get; set; } = 20; // default AMD value - public bool IntegerScalingEnabled { get; set; } = false; + public bool IntegerScalingEnabled { get; set; } public int IntegerScalingDivider { get; set; } = 1; public byte IntegerScalingType { get; set; } = 0; - public bool RISEnabled { get; set; } = false; + public bool RISEnabled { get; set; } public int RISSharpness { get; set; } = 80; // default AMD value - public bool AFMFEnabled { get; set; } = false; + public bool AFMFEnabled { get; set; } // AppCompatFlags public bool FullScreenOptimization { get; set; } = true; diff --git a/HandheldCompanion/Platforms/LibreHardwareMonitor.cs b/HandheldCompanion/Platforms/LibreHardwareMonitor.cs index b73d02530..3e8f5011c 100644 --- a/HandheldCompanion/Platforms/LibreHardwareMonitor.cs +++ b/HandheldCompanion/Platforms/LibreHardwareMonitor.cs @@ -47,7 +47,7 @@ public LibreHardwareMonitor() SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/Platforms/RTSS.cs b/HandheldCompanion/Platforms/RTSS.cs index 021d1d8ef..1d0c80a06 100644 --- a/HandheldCompanion/Platforms/RTSS.cs +++ b/HandheldCompanion/Platforms/RTSS.cs @@ -246,7 +246,11 @@ public void RefreshAppEntry() { // refresh appEntry int processId = appEntry is not null ? appEntry.ProcessId : 0; - appEntry = OSD.GetAppEntries().Where(x => (x.Flags & AppFlags.MASK) != AppFlags.None).FirstOrDefault(a => a.ProcessId == processId); + try + { + appEntry = OSD.GetAppEntries().Where(x => (x.Flags & AppFlags.MASK) != AppFlags.None).FirstOrDefault(a => a.ProcessId == processId); + } + catch (FileNotFoundException) { } } public double GetFramerate(bool refresh = false) diff --git a/HandheldCompanion/Processors/AMDProcessor.cs b/HandheldCompanion/Processors/AMDProcessor.cs index 036acdbec..dc23ac291 100644 --- a/HandheldCompanion/Processors/AMDProcessor.cs +++ b/HandheldCompanion/Processors/AMDProcessor.cs @@ -114,7 +114,7 @@ public override void SetTDPLimit(PowerType type, double limit, bool immediate, i } } - public override void SetGPUClock(double clock, int result) + public override void SetGPUClock(double clock, ref int result) { lock (updateLock) { @@ -147,8 +147,8 @@ public override void SetGPUClock(double clock, int result) if (clock == 12750) return; - int error = RyzenAdj.set_gfx_clk(ry, (uint)clock); - base.SetGPUClock(clock, error); + result = RyzenAdj.set_gfx_clk(ry, (uint)clock); + base.SetGPUClock(clock, ref result); } break; } diff --git a/HandheldCompanion/Processors/IntelProcessor.cs b/HandheldCompanion/Processors/IntelProcessor.cs index 134c16b63..2751cb7f0 100644 --- a/HandheldCompanion/Processors/IntelProcessor.cs +++ b/HandheldCompanion/Processors/IntelProcessor.cs @@ -65,13 +65,13 @@ public void SetMSRLimit(double PL1, double PL2) platform.set_msr_limits((int)PL1, (int)PL2); } - public override void SetGPUClock(double clock, int result) + public override void SetGPUClock(double clock, ref int result) { lock (updateLock) { - var error = platform.set_gfx_clk((int)clock); + result = platform.set_gfx_clk((int)clock); - base.SetGPUClock(clock, error); + base.SetGPUClock(clock, ref result); } } } \ No newline at end of file diff --git a/HandheldCompanion/Processors/Processor.cs b/HandheldCompanion/Processors/Processor.cs index 9d78751fe..46d8cc626 100644 --- a/HandheldCompanion/Processors/Processor.cs +++ b/HandheldCompanion/Processors/Processor.cs @@ -69,7 +69,7 @@ public virtual void SetTDPLimit(PowerType type, double limit, bool immediate = f LogManager.LogDebug("User requested {0} TDP limit: {1}, error code: {2}", type, (uint)limit, result); } - public virtual void SetGPUClock(double clock, int result = 0) + public virtual void SetGPUClock(double clock, ref int result) { /* * #define ADJ_ERR_FAM_UNSUPPORTED -1 diff --git a/HandheldCompanion/Properties/Resources.Designer.cs b/HandheldCompanion/Properties/Resources.Designer.cs index d5aa5c937..0cb12065b 100644 --- a/HandheldCompanion/Properties/Resources.Designer.cs +++ b/HandheldCompanion/Properties/Resources.Designer.cs @@ -3255,6 +3255,78 @@ public static string Enum_SteamDeck_ButtonFlags_OEM1 { } } + /// + /// Looks up a localized string similar to T2. + /// + public static string Enum_TatantulaProController_ButtonFlags_B10 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_B10", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to T3. + /// + public static string Enum_TatantulaProController_ButtonFlags_B11 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_B11", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to C1. + /// + public static string Enum_TatantulaProController_ButtonFlags_B5 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_B5", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to C2. + /// + public static string Enum_TatantulaProController_ButtonFlags_B6 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_B6", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to C3. + /// + public static string Enum_TatantulaProController_ButtonFlags_B7 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_B7", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to C4. + /// + public static string Enum_TatantulaProController_ButtonFlags_B8 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_B8", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to T1. + /// + public static string Enum_TatantulaProController_ButtonFlags_B9 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_B9", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to M. + /// + public static string Enum_TatantulaProController_ButtonFlags_L5 { + get { + return ResourceManager.GetString("Enum_TatantulaProController_ButtonFlags_L5", resourceCulture); + } + } + /// /// Looks up a localized string similar to Gyroscope. /// @@ -4182,6 +4254,24 @@ public static string Hotkey_FunctionDesc { } } + /// + /// Looks up a localized string similar to Open Game Bar. + /// + public static string Hotkey_GameBar { + get { + return ResourceManager.GetString("Hotkey_GameBar", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Press this key: Windows + G. + /// + public static string Hotkey_GameBarDesc { + get { + return ResourceManager.GetString("Hotkey_GameBarDesc", resourceCulture); + } + } + /// /// Looks up a localized string similar to Guide or PS button. /// @@ -4344,6 +4434,24 @@ public static string Hotkey_MainwindowDesc { } } + /// + /// Looks up a localized string similar to Mute volume. + /// + public static string Hotkey_muteVolume { + get { + return ResourceManager.GetString("Hotkey_muteVolume", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mute/unmute volume. + /// + public static string Hotkey_muteVolumeDesc { + get { + return ResourceManager.GetString("Hotkey_muteVolumeDesc", resourceCulture); + } + } + /// /// Looks up a localized string similar to None. /// @@ -4524,6 +4632,24 @@ public static string Hotkey_suspendResumeTaskDesc { } } + /// + /// Looks up a localized string similar to Swap foreground window. + /// + public static string Hotkey_SwapScreen { + get { + return ResourceManager.GetString("Hotkey_SwapScreen", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move the foreground window to next available screen. + /// + public static string Hotkey_SwapScreenDesc { + get { + return ResourceManager.GetString("Hotkey_SwapScreenDesc", resourceCulture); + } + } + /// /// Looks up a localized string similar to Open Task Manager. /// @@ -6798,7 +6924,7 @@ public static string ProfilesPage_EmulatedControllerXbox { } /// - /// Looks up a localized string similar to User per-game profile. + /// Looks up a localized string similar to Use per-game profile. /// public static string ProfilesPage_EnableProfile { get { @@ -7148,6 +7274,15 @@ public static string ProfilesPage_OK { } } + /// + /// Looks up a localized string similar to Display the profile in Quick start section from the Quicktools Applications page. + /// + public static string ProfilesPage_Pinned { + get { + return ResourceManager.GetString("ProfilesPage_Pinned", resourceCulture); + } + } + /// /// Looks up a localized string similar to Power limit target. /// diff --git a/HandheldCompanion/Properties/Resources.resx b/HandheldCompanion/Properties/Resources.resx index 96a47c302..9f1dd6d4a 100644 --- a/HandheldCompanion/Properties/Resources.resx +++ b/HandheldCompanion/Properties/Resources.resx @@ -665,7 +665,7 @@ Delete profile - User per-game profile + Use per-game profile The profile will be automatically applied when the associated application is detected @@ -3309,4 +3309,49 @@ Thank you for using HC and helping us make it better. UNUSED + + Mute volume + + + Mute/unmute volume + + + Open Game Bar + + + Press this key: Windows + G + + + C1 + + + C2 + + + C3 + + + C4 + + + T1 + + + T2 + + + T3 + + + M + + + Display the profile in Quick start section from the Quicktools Applications page + + + Swap foreground window + + + Move the foreground window to next available screen + \ No newline at end of file diff --git a/HandheldCompanion/Properties/Settings.Designer.cs b/HandheldCompanion/Properties/Settings.Designer.cs index ce0a6666b..5bc2c5f73 100644 --- a/HandheldCompanion/Properties/Settings.Designer.cs +++ b/HandheldCompanion/Properties/Settings.Designer.cs @@ -243,7 +243,7 @@ public string CurrentCulture { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("0")] + [global::System.Configuration.DefaultSettingValueAttribute("20")] public double OverlayControllerRestingPitch { get { return ((double)(this["OverlayControllerRestingPitch"])); @@ -1188,5 +1188,17 @@ public double AYANEOFlipScreenBrightness { this["AYANEOFlipScreenBrightness"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool HotkeyRumbleOnExecution { + get { + return ((bool)(this["HotkeyRumbleOnExecution"])); + } + set { + this["HotkeyRumbleOnExecution"] = value; + } + } } } diff --git a/HandheldCompanion/Properties/Settings.settings b/HandheldCompanion/Properties/Settings.settings index 9228258d2..a424c0382 100644 --- a/HandheldCompanion/Properties/Settings.settings +++ b/HandheldCompanion/Properties/Settings.settings @@ -57,7 +57,7 @@ en-US - 0 + 20 False @@ -293,5 +293,8 @@ 100 + + False + \ No newline at end of file diff --git a/HandheldCompanion/Resources/libryzenadj.dll b/HandheldCompanion/Resources/libryzenadj.dll index d32dbe66e..075576f19 100644 Binary files a/HandheldCompanion/Resources/libryzenadj.dll and b/HandheldCompanion/Resources/libryzenadj.dll differ diff --git a/HandheldCompanion/Targets/DualShock4Target.cs b/HandheldCompanion/Targets/DualShock4Target.cs index b09e012a1..c4a0161ac 100644 --- a/HandheldCompanion/Targets/DualShock4Target.cs +++ b/HandheldCompanion/Targets/DualShock4Target.cs @@ -8,7 +8,9 @@ using Nefarius.ViGEm.Client.Targets; using Nefarius.ViGEm.Client.Targets.DualShock4; using System; +using System.Numerics; using System.Threading; +using static HandheldCompanion.Inputs.GyroState; namespace HandheldCompanion.Targets { @@ -177,13 +179,19 @@ public override unsafe void UpdateInputs(ControllerState Inputs, GamepadMotion g calibration = gamepadMotion.GetCalibration(); // Use gyro sensor data, map to proper range, invert where needed - outDS4Report.wGyroX = (short)InputUtils.rangeMap(Inputs.GyroState.Gyroscope[GyroState.SensorState.Raw].X, -2000.0f, 2000.0f, short.MinValue, short.MaxValue); - outDS4Report.wGyroY = (short)InputUtils.rangeMap(Inputs.GyroState.Gyroscope[GyroState.SensorState.Raw].Y, -2000.0f, 2000.0f, short.MinValue, short.MaxValue); - outDS4Report.wGyroZ = (short)InputUtils.rangeMap(Inputs.GyroState.Gyroscope[GyroState.SensorState.Raw].Z, -2000.0f, 2000.0f, short.MinValue, short.MaxValue); + if (Inputs.GyroState.Gyroscope.TryGetValue(SensorState.GamepadMotion, out Vector3 gyrometer)) + { + outDS4Report.wGyroX = (short)InputUtils.rangeMap(gyrometer.X, -2000.0f, 2000.0f, short.MinValue, short.MaxValue); + outDS4Report.wGyroY = (short)InputUtils.rangeMap(gyrometer.Y, -2000.0f, 2000.0f, short.MinValue, short.MaxValue); + outDS4Report.wGyroZ = (short)InputUtils.rangeMap(gyrometer.Z, -2000.0f, 2000.0f, short.MinValue, short.MaxValue); + } - outDS4Report.wAccelX = (short)InputUtils.rangeMap(Inputs.GyroState.Accelerometer[GyroState.SensorState.Raw].X, -4.0f, 4.0f, short.MinValue, short.MaxValue); - outDS4Report.wAccelY = (short)InputUtils.rangeMap(Inputs.GyroState.Accelerometer[GyroState.SensorState.Raw].Y, -4.0f, 4.0f, short.MinValue, short.MaxValue); - outDS4Report.wAccelZ = (short)InputUtils.rangeMap(Inputs.GyroState.Accelerometer[GyroState.SensorState.Raw].Z, -4.0f, 4.0f, short.MinValue, short.MaxValue); + if (Inputs.GyroState.Accelerometer.TryGetValue(SensorState.GamepadMotion, out Vector3 accelerometer)) + { + outDS4Report.wAccelX = (short)InputUtils.rangeMap(accelerometer.X, -4.0f, 4.0f, short.MinValue, short.MaxValue); + outDS4Report.wAccelY = (short)InputUtils.rangeMap(accelerometer.Y, -4.0f, 4.0f, short.MinValue, short.MaxValue); + outDS4Report.wAccelZ = (short)InputUtils.rangeMap(accelerometer.Z, -4.0f, 4.0f, short.MinValue, short.MaxValue); + } // todo: implement battery value based on device outDS4Report.bBatteryLvlSpecial = 11; diff --git a/HandheldCompanion/ViewModels/HotkeyViewModel.cs b/HandheldCompanion/ViewModels/HotkeyViewModel.cs index 6bb795f07..aeb3b20e4 100644 --- a/HandheldCompanion/ViewModels/HotkeyViewModel.cs +++ b/HandheldCompanion/ViewModels/HotkeyViewModel.cs @@ -110,6 +110,7 @@ private void Command_Updated(ICommands command) { OnPropertyChanged(nameof(LiveGlyph)); OnPropertyChanged(nameof(IsEnabled)); + OnPropertyChanged(nameof(IsToggled)); } public override void Dispose() @@ -501,8 +502,7 @@ public HotkeyViewModel(Hotkey hotkey) ExecuteCommand = new DelegateCommand(async () => { - if (Hotkey.command is not null) - Hotkey.command.Execute(Hotkey.command.OnKeyDown, Hotkey.command.OnKeyUp); + Hotkey.Execute(Hotkey.command.OnKeyDown, Hotkey.command.OnKeyUp, false); }); EraseButtonCommand = new DelegateCommand(async () => diff --git a/HandheldCompanion/ViewModels/Layout/Pages/ButtonsPageViewModel.cs b/HandheldCompanion/ViewModels/Layout/Pages/ButtonsPageViewModel.cs index cdb089f26..9edb69fc4 100644 --- a/HandheldCompanion/ViewModels/Layout/Pages/ButtonsPageViewModel.cs +++ b/HandheldCompanion/ViewModels/Layout/Pages/ButtonsPageViewModel.cs @@ -8,7 +8,7 @@ namespace HandheldCompanion.ViewModels { public class ButtonsPageViewModel : ILayoutPageViewModel { - private static readonly List _ABXY = [ButtonFlags.B1, ButtonFlags.B2, ButtonFlags.B3, ButtonFlags.B4, ButtonFlags.B5, ButtonFlags.B6, ButtonFlags.B7, ButtonFlags.B8]; + private static readonly List _ABXY = [ButtonFlags.B1, ButtonFlags.B2, ButtonFlags.B3, ButtonFlags.B4, ButtonFlags.B5, ButtonFlags.B6, ButtonFlags.B7, ButtonFlags.B8, ButtonFlags.B9, ButtonFlags.B10, ButtonFlags.B11]; private static readonly List _BUMPERS = [ButtonFlags.L1, ButtonFlags.R1]; private static readonly List _MENU = [ButtonFlags.Back, ButtonFlags.Start, ButtonFlags.Special, ButtonFlags.Special2]; private static readonly List _BACKGRIPS = [ButtonFlags.L4, ButtonFlags.L5, ButtonFlags.R4, ButtonFlags.R5]; diff --git a/HandheldCompanion/ViewModels/Pages/HotkeyPageViewModel.cs b/HandheldCompanion/ViewModels/Pages/HotkeyPageViewModel.cs index 45fc00fbe..e618b5ba9 100644 --- a/HandheldCompanion/ViewModels/Pages/HotkeyPageViewModel.cs +++ b/HandheldCompanion/ViewModels/Pages/HotkeyPageViewModel.cs @@ -12,6 +12,18 @@ public class HotkeyPageViewModel : BaseViewModel public ObservableCollection HotkeysList { get; set; } = []; public ICommand CreateHotkeyCommand { get; private set; } + public bool Rumble + { + get + { + return SettingsManager.GetBoolean("HotkeyRumbleOnExecution"); + } + set + { + SettingsManager.SetProperty("HotkeyRumbleOnExecution", value); + } + } + public HotkeyPageViewModel() { HotkeysManager.Updated += HotkeysManager_Updated; diff --git a/HandheldCompanion/ViewModels/Pages/OverlayPageViewModel.cs b/HandheldCompanion/ViewModels/Pages/OverlayPageViewModel.cs index 9d0ebc1d0..bf69fe06e 100644 --- a/HandheldCompanion/ViewModels/Pages/OverlayPageViewModel.cs +++ b/HandheldCompanion/ViewModels/Pages/OverlayPageViewModel.cs @@ -526,7 +526,7 @@ public override void Dispose() base.Dispose(); } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/ViewModels/Pages/PerformancePageViewModel.cs b/HandheldCompanion/ViewModels/Pages/PerformancePageViewModel.cs index 5eea8af30..b72713f96 100644 --- a/HandheldCompanion/ViewModels/Pages/PerformancePageViewModel.cs +++ b/HandheldCompanion/ViewModels/Pages/PerformancePageViewModel.cs @@ -489,7 +489,7 @@ public PerformancePageViewModel(bool isQuickTools) #region General Setup - SettingsManager.SettingValueChanged += SettingsManager_SettingsValueChanged; + SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; MultimediaManager.PrimaryScreenChanged += MultimediaManager_PrimaryScreenChanged; PerformanceManager.ProcessorStatusChanged += PerformanceManager_ProcessorStatusChanged; PerformanceManager.EPPChanged += PerformanceManager_EPPChanged; @@ -668,7 +668,7 @@ public PerformancePageViewModel(bool isQuickTools) public override void Dispose() { - SettingsManager.SettingValueChanged -= SettingsManager_SettingsValueChanged; + SettingsManager.SettingValueChanged -= SettingsManager_SettingValueChanged; MultimediaManager.PrimaryScreenChanged -= MultimediaManager_PrimaryScreenChanged; PerformanceManager.ProcessorStatusChanged -= PerformanceManager_ProcessorStatusChanged; PerformanceManager.EPPChanged += PerformanceManager_EPPChanged; @@ -690,7 +690,7 @@ public override void Dispose() #region Events - private void SettingsManager_SettingsValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { switch (name) { diff --git a/HandheldCompanion/ViewModels/Pages/QuickApplicationPageViewModel.cs b/HandheldCompanion/ViewModels/Pages/QuickApplicationPageViewModel.cs index 146ed9667..5b3e086eb 100644 --- a/HandheldCompanion/ViewModels/Pages/QuickApplicationPageViewModel.cs +++ b/HandheldCompanion/ViewModels/Pages/QuickApplicationPageViewModel.cs @@ -99,11 +99,15 @@ private void ProfileManager_Updated(Profile profile, UpdateSource source, bool i ProfileViewModel? foundProfile = Profiles.ToList().FirstOrDefault(p => p.Profile == profile || p.Profile.Guid == profile.Guid); if (foundProfile is null) { - Profiles.SafeAdd(new ProfileViewModel(profile, this)); + if (profile.IsPinned) + Profiles.SafeAdd(new ProfileViewModel(profile, this)); } else { - foundProfile.Profile = profile; + if (profile.IsPinned) + foundProfile.Profile = profile; + else + ProfileManager_Deleted(profile); } } diff --git a/HandheldCompanion/ViewModels/ProcessWindowViewModel.cs b/HandheldCompanion/ViewModels/ProcessWindowViewModel.cs index 4f67dcb56..71e095a4c 100644 --- a/HandheldCompanion/ViewModels/ProcessWindowViewModel.cs +++ b/HandheldCompanion/ViewModels/ProcessWindowViewModel.cs @@ -66,6 +66,9 @@ public ProcessWindowViewModel(ProcessWindow processWindow, ProcessExViewModel pr SwapScreenCommand = new DelegateCommand(async () => { Screen screen = Screen.AllScreens.Where(screen => screen.DeviceName != CurrentScreen.DeviceName).FirstOrDefault(); + if (screen is null) + return; + WinAPI.MoveWindow(ProcessWindow.Hwnd, screen, WpfScreenHelper.Enum.WindowPositions.Maximize); WinAPI.SetForegroundWindow(ProcessWindow.Hwnd); diff --git a/HandheldCompanion/Views/Pages/ControllerPage.xaml.cs b/HandheldCompanion/Views/Pages/ControllerPage.xaml.cs index 365d03d0d..a06f5d0dc 100644 --- a/HandheldCompanion/Views/Pages/ControllerPage.xaml.cs +++ b/HandheldCompanion/Views/Pages/ControllerPage.xaml.cs @@ -63,7 +63,7 @@ private void ProfileManager_Applied(Profile profile, UpdateSource source) } }); } - private void SettingsManager_SettingValueChanged(string name, object value) + private void SettingsManager_SettingValueChanged(string name, object value, bool temporary) { // UI thread Application.Current.Dispatcher.Invoke(() => diff --git a/HandheldCompanion/Views/Pages/DevicePage.xaml.cs b/HandheldCompanion/Views/Pages/DevicePage.xaml.cs index c6b1ec11b..951a98820 100644 --- a/HandheldCompanion/Views/Pages/DevicePage.xaml.cs +++ b/HandheldCompanion/Views/Pages/DevicePage.xaml.cs @@ -116,7 +116,7 @@ private void Page_Loaded(object? sender, RoutedEventArgs? e) public void Page_Closed() { } - private void SettingsManager_SettingValueChanged(string? name, object value) + private void SettingsManager_SettingValueChanged(string? name, object value, bool temporary) { // UI thread Application.Current.Dispatcher.Invoke(() => diff --git a/HandheldCompanion/Views/Pages/HotkeysPage.xaml b/HandheldCompanion/Views/Pages/HotkeysPage.xaml index ef6de194c..1536c0af6 100644 --- a/HandheldCompanion/Views/Pages/HotkeysPage.xaml +++ b/HandheldCompanion/Views/Pages/HotkeysPage.xaml @@ -23,264 +23,276 @@ mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text="{Binding Description, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" + TextWrapping="Wrap" /> - + - + Orientation="Horizontal"> - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + - - - - + - - - - - - + + + + + + + + + - - + + - - - - - + + + + + + - - - - + + + + - - + + + + + + + - - + + - - @@ -288,46 +300,233 @@ - + - - - - - - - - - - - - - - - - - - - - + Text="{Binding CustomName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + Text="{Binding ExecutableArguments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" + TextAlignment="Left" /> - - - - + @@ -389,129 +583,44 @@ Margin="12,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" - SelectedIndex="{Binding CyclingDirection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> - - + SelectedIndex="{Binding ExecutableWindowStyle, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> + Normal + Hidden + Minimized + Maximized - - - - - - - - - - - - - - - + + - + - - + + - - + + - + Text="Specifies if the application should start with adminstrator elevation" + TextWrapping="Wrap" /> + - + VerticalAlignment="Center" + IsOn="{Binding ExecutableRunAs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" + Style="{DynamicResource InvertedToggleSwitchStyle}" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - Normal - Hidden - Minimized - Maximized - - - - - - - - - - - - - - - - - - + Command="{Binding DeleteHotkeyCommand}" + Content="Delete hotkey" + Style="{StaticResource AccentButtonStyle}" /> - - - - - - - - - - -