From 33a981d9ecdc3e3f4904de9b49c13e9d13f84eb3 Mon Sep 17 00:00:00 2001 From: CasperH2O Date: Fri, 19 Apr 2024 14:49:16 +0200 Subject: [PATCH] Move files from EA to Public. --- HandheldCompanion/ADLX/ADLXBackend.cs | 9 +- HandheldCompanion/App.xaml | 162 +- HandheldCompanion/App.xaml.cs | 3 + .../Devices/AYANEO/AYANEODevice.cs | 52 - .../Devices/AYANEO/AYANEODeviceCEc.cs | 34 +- .../Devices/AYANEO/AYANEODeviceCEii.cs | 21 +- .../GraphicsProcessingUnit/AMDGPU.cs | 81 +- .../GraphicsProcessingUnit/GPU.cs | 64 +- .../GraphicsProcessingUnit/IntelGPU.cs | 28 +- HandheldCompanion/HandheldCompanion.csproj | 1 + HandheldCompanion/HandheldCompanion.json | 4 +- HandheldCompanion/IGCL/IGCLBackend.cs | 151 +- .../Managers/ControllerManager.cs | 46 +- HandheldCompanion/Managers/GPUManager.cs | 108 +- .../Managers/MultimediaManager.cs | 1168 +++++------ HandheldCompanion/Managers/ProfileManager.cs | 1722 +++++++++-------- HandheldCompanion/Misc/MotherboardInfo.cs | 757 ++++---- .../Platforms/LibreHardwareMonitor.cs | 39 +- HandheldCompanion/Processors/AMDProcessor.cs | 80 +- .../Processors/IntelProcessor.cs | 82 +- HandheldCompanion/Processors/Processor.cs | 76 +- HandheldCompanion/UI/UISounds.cs | 3 + HandheldCompanion/Utils/CrossThreadLock.cs | 5 + HandheldCompanion/Utils/WPFUtils.cs | 748 +++---- .../Views/Classes/GamepadWindow.cs | 224 +-- .../Views/Pages/ControllerPage.xaml | 72 +- .../Views/Pages/ControllerPage.xaml.cs | 22 +- .../Views/Pages/LayoutPage.xaml.cs | 7 +- .../Views/Pages/PerformancePage.xaml | 1690 ++++++++-------- .../Views/Pages/PerformancePage.xaml.cs | 48 +- .../Pages/Profiles/SettingsMode0.xaml.cs | 102 +- .../Pages/Profiles/SettingsMode1.xaml.cs | 37 +- .../Views/Pages/ProfilesPage.xaml.cs | 495 ++--- .../Views/QuickPages/QuickOverlayPage.xaml | 666 +++---- .../Views/QuickPages/QuickOverlayPage.xaml.cs | 26 +- .../QuickPages/QuickPerformancePage.xaml | 1208 ++++++------ .../QuickPages/QuickPerformancePage.xaml.cs | 40 +- .../QuickPages/QuickProfilesPage.xaml.cs | 7 - .../Views/Windows/MainWindow.xaml.cs | 1598 ++++++++------- README.md | 2 +- 40 files changed, 5733 insertions(+), 5955 deletions(-) diff --git a/HandheldCompanion/ADLX/ADLXBackend.cs b/HandheldCompanion/ADLX/ADLXBackend.cs index 64d3ca68d..b3a9aa0c2 100644 --- a/HandheldCompanion/ADLX/ADLXBackend.cs +++ b/HandheldCompanion/ADLX/ADLXBackend.cs @@ -3,7 +3,7 @@ namespace HandheldCompanion.ADLX { - public class ADLXBackend + public static class ADLXBackend { public const string ADLX_Wrapper = @"ADLX_Wrapper.dll"; @@ -123,12 +123,5 @@ public enum ADLX_RESULT [DllImport(ADLX_Wrapper, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetScalingMode(int displayIdx, int mode); [DllImport(ADLX_Wrapper, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetAdlxTelemetry(int GPU, ref AdlxTelemetryData adlxTelemetryData); - - internal static AdlxTelemetryData GetTelemetryData() - { - AdlxTelemetryData TelemetryData = new(); - bool Result = GetAdlxTelemetry(0, ref TelemetryData); - return TelemetryData; - } } } diff --git a/HandheldCompanion/App.xaml b/HandheldCompanion/App.xaml index e52ae8850..82527e020 100644 --- a/HandheldCompanion/App.xaml +++ b/HandheldCompanion/App.xaml @@ -1,82 +1,82 @@ - - - - - - - - - - - - - 0 - - - - 0 - - - - - - - - - - - - - 0 - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + 0 + + + + 0 + + + + + + + + + + + + + 0 + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandheldCompanion/App.xaml.cs b/HandheldCompanion/App.xaml.cs index dc1168cf4..e67b73c86 100644 --- a/HandheldCompanion/App.xaml.cs +++ b/HandheldCompanion/App.xaml.cs @@ -36,6 +36,9 @@ protected override void OnStartup(StartupEventArgs args) var CurrentAssembly = Assembly.GetExecutingAssembly(); var fileVersionInfo = FileVersionInfo.GetVersionInfo(CurrentAssembly.Location); + // set environment variables + Environment.SetEnvironmentVariable("APP_BASE_DIRECTORY", AppContext.BaseDirectory); + // initialize log LogManager.Initialize("HandheldCompanion"); LogManager.LogInformation("{0} ({1})", CurrentAssembly.GetName(), fileVersionInfo.FileVersion); diff --git a/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs b/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs index 361f97025..6658cfdd7 100644 --- a/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs +++ b/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs @@ -14,14 +14,8 @@ protected enum LEDGroup AYA = 4, } - private int prevBatteryLevelPercentage; - private PowerLineStatus prevPowerStatus; - public AYANEODevice() { - this.prevPowerStatus = SystemInformation.PowerStatus.PowerLineStatus; - this.prevBatteryLevelPercentage = (int)(SystemInformation.PowerStatus.BatteryLifePercent * 100); - SystemManager.PowerStatusChanged += PowerManager_PowerStatusChanged; } public override string GetGlyph(ButtonFlags button) @@ -44,51 +38,5 @@ public override string GetGlyph(ButtonFlags button) return defaultGlyph; } - - private void PowerManager_PowerStatusChanged(PowerStatus powerStatus) - { - // Ayaneo devices automatically set LED color and or effect in the following scenarios - // - Plugged in, charging - // - Fully charged, battery 100% - // - Battery almost empty, battery 5% or less - // This function overrides this change based on settings - - // Get power information and bettery as a % of 100 - int currentBatteryLevelPercentage = (int)(powerStatus.BatteryLifePercent * 100); - - // Check if the device went from battery to charging - if (powerStatus.PowerLineStatus == PowerLineStatus.Online && this.prevPowerStatus == PowerLineStatus.Offline) - { - LogManager.LogDebug("Ayaneo LED, device went from battery to charging, apply color"); - base.PowerStatusChange(this); - } - - // Check if the device went from charging to battery - if (powerStatus.PowerLineStatus == PowerLineStatus.Offline && this.prevPowerStatus == PowerLineStatus.Online) - { - LogManager.LogDebug("Ayaneo LED, device went from charging to battery, apply color"); - base.PowerStatusChange(this); - } - - // Check for the battery level change scenarios - - // Check if the battery went from 99 or lower to 100 - if (this.prevBatteryLevelPercentage <= 99 && currentBatteryLevelPercentage >= 100) - { - LogManager.LogDebug("Ayaneo LED, device went from < 99% battery to 100%, apply color"); - base.PowerStatusChange(this); - } - - // Check if the battery went from 6 or higher to 5 or lower - if (this.prevBatteryLevelPercentage >= 6 && currentBatteryLevelPercentage <= 5) - { - LogManager.LogDebug("Ayaneo LED, device went from > 5% battery <= 5%, apply color"); - base.PowerStatusChange(this); - } - - // Track battery level % and power status for next round - this.prevBatteryLevelPercentage = currentBatteryLevelPercentage; - this.prevPowerStatus = powerStatus.PowerLineStatus; - } } } diff --git a/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEc.cs b/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEc.cs index 166c4dbe6..376b9943d 100644 --- a/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEc.cs +++ b/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEc.cs @@ -76,6 +76,25 @@ public AYANEODeviceCEc() )); } + public override bool Open() + { + if (!base.Open()) return false; + lock (this.updateLock) + { + this.CEcControl_RgbHoldControl(); + } + return true; + } + + public override void Close() + { + lock (this.updateLock) + { + this.CEcControl_RgbReleaseControl(); + } + base.Close(); + } + public override bool SetLedStatus(bool status) { lock (this.updateLock) @@ -159,11 +178,6 @@ private void CEcRgb_GlobalOff(LEDGroup group) this.CEcRgb_I2cWrite(group, 0x02, 0xc0); } - private void CEcRgb_SlowOff(LEDGroup group) - { - this.CEcRgb_I2cWrite(group, 0x02, 0xc0 + (byte)'\a'); - } - private void CEcRgb_SetColorAll(LEDGroup group, Color color) { foreach (int zone in this.rgbZones) @@ -215,5 +229,15 @@ protected virtual void CEcControl_RgbI2cWrite(LEDGroup group, byte command, byte Thread.Sleep(5); // AYASpace does this so copied it here this.ECRAMWrite(0xbf, 0xfe); } + + protected virtual void CEcControl_RgbHoldControl() + { + this.ECRAMWrite(0xbf, 0xfe); + } + + protected virtual void CEcControl_RgbReleaseControl() + { + this.ECRAMWrite(0xbf, 0x00); + } } } diff --git a/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEii.cs b/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEii.cs index 3cf4d0edc..f1d73153a 100644 --- a/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEii.cs +++ b/HandheldCompanion/Devices/AYANEO/AYANEODeviceCEii.cs @@ -99,15 +99,30 @@ protected override void CEcControl_RgbI2cWrite(LEDGroup group, byte command, byt Thread.Sleep(10); // AYASpace does this so copied it here } + public bool ECRamDirectWrite(byte address, byte data, byte offset = 0xd1) + { + ushort address2 = BitConverter.ToUInt16(new byte[] { (byte)address, offset }, 0); + return base.ECRamDirectWrite(address2, this.ECDetails, data); + } + + protected override void CEcControl_RgbHoldControl() + { + this.CEiiEcHelper_RgbStart(); + } + + protected override void CEcControl_RgbReleaseControl() + { + this.CEiiEcHelper_RgbStop(); + } + protected void CEiiEcHelper_RgbStart() { this.ECRamDirectWrite(0x87, 0xa5); } - public bool ECRamDirectWrite(byte address, byte data, byte offset = 0xd1) + protected void CEiiEcHelper_RgbStop() { - ushort address2 = BitConverter.ToUInt16(new byte[] { (byte)address, offset }, 0); - return base.ECRamDirectWrite(address2, this.ECDetails, data); + this.ECRamDirectWrite(0x87, 0x00); } } } diff --git a/HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs b/HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs index 6154f72fd..418a298ad 100644 --- a/HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs +++ b/HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs @@ -3,7 +3,7 @@ using SharpDX.Direct3D9; using System; using System.Text; -using System.Threading.Tasks; +using System.Threading; using System.Timers; using static HandheldCompanion.ADLX.ADLXBackend; using Timer = System.Timers.Timer; @@ -21,6 +21,8 @@ public class AMDGPU : GPU private bool prevRSR = false; private int prevRSRSharpness = -1; + protected new AdlxTelemetryData TelemetryData = new(); + public bool HasRSRSupport() { if (!IsInitialized) @@ -130,20 +132,17 @@ public bool SetRSR(bool enable) if (!IsInitialized) return false; - return Execute(() => + // mutually exclusive + if (enable) { - // mutually exclusive - if (enable) - { - if (ADLXBackend.GetIntegerScaling(displayIdx)) - ADLXBackend.SetIntegerScaling(displayIdx, false); - - if (ADLXBackend.GetImageSharpening(deviceIdx)) - ADLXBackend.SetImageSharpening(deviceIdx, false); - } + if (GetIntegerScaling()) + SetIntegerScaling(false); + + if (GetImageSharpening()) + SetImageSharpening(false); + } - return ADLXBackend.SetRSR(enable); - }, false); + return Execute(() => ADLXBackend.SetRSR(enable), false); } public override bool SetImageSharpeningSharpness(int sharpness) @@ -159,17 +158,14 @@ public override bool SetIntegerScaling(bool enabled, byte type = 0) if (!IsInitialized) return false; - return Execute(() => + // mutually exclusive + if (enabled) { - // mutually exclusive - if (enabled) - { - if (ADLXBackend.GetRSR()) - ADLXBackend.SetRSR(false); - } + if (GetRSR()) + SetRSR(false); + } - return ADLXBackend.SetIntegerScaling(displayIdx, enabled); - }, false); + return Execute(() => ADLXBackend.SetIntegerScaling(displayIdx, enabled), false); } public override bool SetGPUScaling(bool enabled) @@ -188,6 +184,18 @@ public override bool SetScalingMode(int mode) return Execute(() => ADLXBackend.SetScalingMode(displayIdx, mode), false); } + private AdlxTelemetryData GetTelemetry() + { + if (!IsInitialized) + return TelemetryData; + + return Execute(() => + { + ADLXBackend.GetAdlxTelemetry(deviceIdx, ref TelemetryData); + return TelemetryData; + }, TelemetryData); + } + public override float GetClock() { return (float)TelemetryData.gpuClockSpeedValue; @@ -213,8 +221,6 @@ public override float GetVRAMUsage() return (float)TelemetryData.gpuVramValue; } - protected AdlxTelemetryData TelemetryData = new(); - public AMDGPU(AdapterInformation adapterInformation) : base(adapterInformation) { ADLX_RESULT result = ADLX_RESULT.ADLX_FAIL; @@ -271,10 +277,12 @@ public AMDGPU(AdapterInformation adapterInformation) : base(adapterInformation) private void TelemetryTimer_Elapsed(object? sender, ElapsedEventArgs e) { - if (telemetryLock.TryEnter()) + if (halting) + return; + + lock (telemetryLock) { - TelemetryData = GetTelemetryData(); - telemetryLock.Exit(); + TelemetryData = GetTelemetry(); } } @@ -286,14 +294,17 @@ public override void Start() base.Start(); } - public override async void Stop() + public override void Stop() { base.Stop(); } - private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) + private void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) { - if (updateLock.TryEnter()) + if (halting) + return; + + lock (updateLock) { bool GPUScaling = false; @@ -331,11 +342,11 @@ private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) bool RSR = false; int RSRSharpness = GetRSRSharpness(); - DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); + DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(2)); while (DateTime.Now < timeout && !RSRSupport) { RSRSupport = HasRSRSupport(); - await Task.Delay(250); + Thread.Sleep(250); } RSR = GetRSR(); @@ -357,11 +368,11 @@ private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) bool IntegerScalingSupport = false; bool IntegerScaling = false; - DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); + DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(2)); while (DateTime.Now < timeout && !IntegerScalingSupport) { IntegerScalingSupport = HasIntegerScalingSupport(); - await Task.Delay(250); + Thread.Sleep(250); } IntegerScaling = GetIntegerScaling(); @@ -391,8 +402,6 @@ private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) } } catch { } - - updateLock.Exit(); } } } diff --git a/HandheldCompanion/GraphicsProcessingUnit/GPU.cs b/HandheldCompanion/GraphicsProcessingUnit/GPU.cs index 3e77665d8..d3a2aeba2 100644 --- a/HandheldCompanion/GraphicsProcessingUnit/GPU.cs +++ b/HandheldCompanion/GraphicsProcessingUnit/GPU.cs @@ -1,4 +1,4 @@ -using HandheldCompanion.Utils; +using HandheldCompanion.Managers; using SharpDX.Direct3D9; using System; using System.Management; @@ -27,7 +27,7 @@ public class GPU : IDisposable public bool IsInitialized = false; - protected const int UpdateInterval = 2000; + protected const int UpdateInterval = 5000; protected Timer UpdateTimer; protected const int TelemetryInterval = 1000; @@ -44,35 +44,37 @@ public class GPU : IDisposable protected bool prevImageSharpening = false; protected int prevImageSharpeningSharpness = -1; - protected CrossThreadLock updateLock = new(); - protected CrossThreadLock telemetryLock = new(); - protected bool halting = false; + protected static bool halting = false; + protected static object updateLock = new(); + protected static object telemetryLock = new(); + public static object functionLock = new(); protected T Execute(Func func, T defaultValue) { - if (halting) - return defaultValue; - - try - { - Task task = Task.Run(() => + if (!halting && GPUManager.IsInitialized) + try { - return func(); - }); - - if (task.Wait(TimeSpan.FromSeconds(3))) - return task.Result; - } - catch (AccessViolationException) - { } - catch (Exception) - { } + Task task = Task.Run(() => + { + lock (functionLock) + { + if (!halting && GPUManager.IsInitialized) + return func(); + else + return defaultValue; + } + }); + if (task.Wait(TimeSpan.FromSeconds(1))) + return task.Result; + } + catch (AccessViolationException) + { } + catch (Exception) + { } return defaultValue; } - public bool IsBusy => (UpdateTimer is not null && UpdateTimer.Enabled) || (TelemetryTimer is not null && TelemetryTimer.Enabled); - public GPU(AdapterInformation adapterInformation) { this.adapterInformation = adapterInformation; @@ -95,17 +97,11 @@ public virtual void Stop() // set halting flag halting = true; - try - { - if (UpdateTimer != null && UpdateTimer.Enabled) - UpdateTimer.Stop(); + if (UpdateTimer != null && UpdateTimer.Enabled) + UpdateTimer.Stop(); - if (TelemetryTimer != null && TelemetryTimer.Enabled) - TelemetryTimer.Stop(); - } - catch (Exception ex) - { - } + if (TelemetryTimer != null && TelemetryTimer.Enabled) + TelemetryTimer.Stop(); } protected virtual void OnIntegerScalingChanged(bool supported, bool enabled) @@ -231,8 +227,6 @@ public void Dispose() { UpdateTimer?.Dispose(); TelemetryTimer?.Dispose(); - updateLock?.Dispose(); - telemetryLock?.Dispose(); GC.SuppressFinalize(this); } diff --git a/HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs b/HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs index 491bde9f3..294703a0f 100644 --- a/HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs +++ b/HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs @@ -11,6 +11,8 @@ public class IntelGPU : GPU #region events #endregion + protected new ctl_telemetry_data TelemetryData = new(); + public override bool HasIntegerScalingSupport() { if (!IsInitialized) @@ -86,6 +88,9 @@ public override bool SetImageSharpening(bool enable) public override bool SetImageSharpeningSharpness(int sharpness) { + if (!IsInitialized) + return false; + return Execute(() => IGCLBackend.SetImageSharpeningSharpness(deviceIdx, 0, sharpness), false); } @@ -105,6 +110,17 @@ public override bool SetIntegerScaling(bool enabled, byte type) return Execute(() => IGCLBackend.SetIntegerScaling(deviceIdx, enabled, type), false); } + private ctl_telemetry_data GetTelemetry() + { + if (!IsInitialized) + return TelemetryData; + + return Execute(() => + { + return IGCLBackend.GetTelemetry(deviceIdx); + }, TelemetryData); + } + public override float GetClock() { return (float)TelemetryData.GpuCurrentClockFrequencyValue; @@ -125,8 +141,6 @@ public override float GetTemperature() return (float)TelemetryData.GpuCurrentTemperatureValue; } - protected ctl_telemetry_data TelemetryData = new(); - public IntelGPU(AdapterInformation adapterInformation) : base(adapterInformation) { deviceIdx = GetDeviceIdx(adapterInformation.Details.Description); @@ -142,10 +156,12 @@ public IntelGPU(AdapterInformation adapterInformation) : base(adapterInformation private void TelemetryTimer_Elapsed(object? sender, ElapsedEventArgs e) { - if (telemetryLock.TryEnter()) + if (halting) + return; + + lock (telemetryLock) { - TelemetryData = GetTelemetry(deviceIdx); - telemetryLock.Exit(); + TelemetryData = GetTelemetry(); } } @@ -157,7 +173,7 @@ public override void Start() base.Start(); } - public override async void Stop() + public override void Stop() { base.Stop(); } diff --git a/HandheldCompanion/HandheldCompanion.csproj b/HandheldCompanion/HandheldCompanion.csproj index 63d535ae3..b6e102c4b 100644 --- a/HandheldCompanion/HandheldCompanion.csproj +++ b/HandheldCompanion/HandheldCompanion.csproj @@ -150,6 +150,7 @@ + diff --git a/HandheldCompanion/HandheldCompanion.json b/HandheldCompanion/HandheldCompanion.json index 5f131a3ad..5dfad5602 100644 --- a/HandheldCompanion/HandheldCompanion.json +++ b/HandheldCompanion/HandheldCompanion.json @@ -13,7 +13,7 @@ { "Name": "File", "Args": { - "path": ".\\logs\\HandheldCompanion-.log", + "path": "%APP_BASE_DIRECTORY%\\logs\\HandheldCompanion-.log", "rollingInterval": "Day" } }, @@ -25,4 +25,4 @@ } ] } -} \ No newline at end of file +} diff --git a/HandheldCompanion/IGCL/IGCLBackend.cs b/HandheldCompanion/IGCL/IGCLBackend.cs index fdffd2a7d..2fecb4039 100644 --- a/HandheldCompanion/IGCL/IGCLBackend.cs +++ b/HandheldCompanion/IGCL/IGCLBackend.cs @@ -4,7 +4,7 @@ namespace HandheldCompanion.IGCL { // Define the wrapper class for IGCL - public class IGCLBackend + public static class IGCLBackend { // Define the types used by the C++ functions [StructLayout(LayoutKind.Sequential)] @@ -410,20 +410,20 @@ public struct ctl_telemetry_data private delegate ctl_result_t GetTelemetryDataDelegate(ctl_device_adapter_handle_t hDevice, ref ctl_telemetry_data TelemetryData); // Define the function pointers - private static InitializeIgclDelegate InitializeIgcl; - private static CloseIgclDelegate CloseIgcl; - private static EnumerateDevicesDelegate EnumerateDevices; - private static GetDevicePropertiesDelegate GetDeviceProperties; - private static GetRetroScalingCapsDelegate GetRetroScalingCaps; - private static GetRetroScalingSettingsDelegate GetRetroScalingSettings; - private static SetRetroScalingSettingsDelegate SetRetroScalingSettings; - private static GetScalingCapsDelegate GetScalingCaps; - private static GetScalingSettingsDelegate GetScalingSettings; - private static SetScalingSettingsDelegate SetScalingSettings; - private static GetSharpnessCapsDelegate GetSharpnessCaps; - private static GetSharpnessSettingsDelegate GetSharpnessSettings; - private static SetSharpnessSettingsDelegate SetSharpnessSettings; - private static GetTelemetryDataDelegate GetTelemetryData; + private static InitializeIgclDelegate? InitializeIgcl; + private static CloseIgclDelegate? CloseIgcl; + private static EnumerateDevicesDelegate? EnumerateDevices; + private static GetDevicePropertiesDelegate? GetDeviceProperties; + private static GetRetroScalingCapsDelegate? GetRetroScalingCaps; + private static GetRetroScalingSettingsDelegate? GetRetroScalingSettings; + private static SetRetroScalingSettingsDelegate? SetRetroScalingSettings; + private static GetScalingCapsDelegate? GetScalingCaps; + private static GetScalingSettingsDelegate? GetScalingSettings; + private static SetScalingSettingsDelegate? SetScalingSettings; + private static GetSharpnessCapsDelegate? GetSharpnessCaps; + private static GetSharpnessSettingsDelegate? GetSharpnessSettings; + private static SetSharpnessSettingsDelegate? SetSharpnessSettings; + private static GetTelemetryDataDelegate? GetTelemetryData; public static IntPtr[] devices = new IntPtr[1] { IntPtr.Zero }; private static IntPtr pDll = IntPtr.Zero; @@ -434,12 +434,68 @@ public enum IGCLStatus NO_ERROR = 0, DLL_NOT_FOUND = 1, DLL_INCORRECT_VERSION = 2, - DLL_INITIALIZE_ERROR = 3 + DLL_INITIALIZE_ERROR = 3, + DLL_INITIALIZE_SUCCESS = 4 } private const string dllName = "IGCL_Wrapper.dll"; private static IGCLStatus status = IGCLStatus.NO_ERROR; + static IGCLBackend() + { + if (pDll == IntPtr.Zero) + { + pDll = LoadLibrary(dllName); + if (pDll == IntPtr.Zero) + { + status = IGCLStatus.DLL_NOT_FOUND; + } + + if (status == IGCLStatus.NO_ERROR) + { + try + { + // Get the function pointers + InitializeIgcl = (InitializeIgclDelegate)GetDelegate("IntializeIgcl", typeof(InitializeIgclDelegate)); + CloseIgcl = (CloseIgclDelegate)GetDelegate("CloseIgcl", typeof(CloseIgclDelegate)); + EnumerateDevices = (EnumerateDevicesDelegate)GetDelegate("EnumerateDevices", typeof(EnumerateDevicesDelegate)); + GetDeviceProperties = (GetDevicePropertiesDelegate)GetDelegate("GetDeviceProperties", typeof(GetDevicePropertiesDelegate)); + GetRetroScalingCaps = (GetRetroScalingCapsDelegate)GetDelegate("GetRetroScalingCaps", typeof(GetRetroScalingCapsDelegate)); + GetRetroScalingSettings = (GetRetroScalingSettingsDelegate)GetDelegate("GetRetroScalingSettings", typeof(GetRetroScalingSettingsDelegate)); + SetRetroScalingSettings = (SetRetroScalingSettingsDelegate)GetDelegate("SetRetroScalingSettings", typeof(SetRetroScalingSettingsDelegate)); + GetScalingCaps = (GetScalingCapsDelegate)GetDelegate("GetScalingCaps", typeof(GetScalingCapsDelegate)); + GetScalingSettings = (GetScalingSettingsDelegate)GetDelegate("GetScalingSettings", typeof(GetScalingSettingsDelegate)); + SetScalingSettings = (SetScalingSettingsDelegate)GetDelegate("SetScalingSettings", typeof(SetScalingSettingsDelegate)); + GetSharpnessCaps = (GetSharpnessCapsDelegate)GetDelegate("GetSharpnessCaps", typeof(GetSharpnessCapsDelegate)); + GetSharpnessSettings = (GetSharpnessSettingsDelegate)GetDelegate("GetSharpnessSettings", typeof(GetSharpnessSettingsDelegate)); + SetSharpnessSettings = (SetSharpnessSettingsDelegate)GetDelegate("SetSharpnessSettings", typeof(SetSharpnessSettingsDelegate)); + GetTelemetryData = (GetTelemetryDataDelegate)GetDelegate("GetTelemetryData", typeof(GetTelemetryDataDelegate)); + + status = IGCLStatus.DLL_INITIALIZE_SUCCESS; + } + catch + { + status = IGCLStatus.DLL_INITIALIZE_ERROR; + + InitializeIgcl = null; + CloseIgcl = null; + EnumerateDevices = null; + GetDeviceProperties = null; + GetRetroScalingCaps = null; + GetRetroScalingSettings = null; + SetRetroScalingSettings = null; + GetScalingCaps = null; + GetScalingSettings = null; + SetScalingSettings = null; + GetSharpnessCaps = null; + GetSharpnessSettings = null; + SetSharpnessSettings = null; + GetTelemetryData = null; + } + } + } + } + private static Delegate GetDelegate(string procName, Type delegateType) { IntPtr ptr = GetProcAddress(pDll, procName); @@ -455,53 +511,28 @@ private static Delegate GetDelegate(string procName, Type delegateType) public static bool Initialize() { - pDll = LoadLibrary(dllName); - if (pDll == IntPtr.Zero) - { - status = IGCLStatus.DLL_NOT_FOUND; - } - else + if (status == IGCLStatus.DLL_INITIALIZE_SUCCESS) { - try - { - // Get the function pointers - InitializeIgcl = (InitializeIgclDelegate)GetDelegate("IntializeIgcl", typeof(InitializeIgclDelegate)); - CloseIgcl = (CloseIgclDelegate)GetDelegate("CloseIgcl", typeof(CloseIgclDelegate)); - EnumerateDevices = (EnumerateDevicesDelegate)GetDelegate("EnumerateDevices", typeof(EnumerateDevicesDelegate)); - GetDeviceProperties = (GetDevicePropertiesDelegate)GetDelegate("GetDeviceProperties", typeof(GetDevicePropertiesDelegate)); - GetRetroScalingCaps = (GetRetroScalingCapsDelegate)GetDelegate("GetRetroScalingCaps", typeof(GetRetroScalingCapsDelegate)); - GetRetroScalingSettings = (GetRetroScalingSettingsDelegate)GetDelegate("GetRetroScalingSettings", typeof(GetRetroScalingSettingsDelegate)); - SetRetroScalingSettings = (SetRetroScalingSettingsDelegate)GetDelegate("SetRetroScalingSettings", typeof(SetRetroScalingSettingsDelegate)); - GetScalingCaps = (GetScalingCapsDelegate)GetDelegate("GetScalingCaps", typeof(GetScalingCapsDelegate)); - GetScalingSettings = (GetScalingSettingsDelegate)GetDelegate("GetScalingSettings", typeof(GetScalingSettingsDelegate)); - SetScalingSettings = (SetScalingSettingsDelegate)GetDelegate("SetScalingSettings", typeof(SetScalingSettingsDelegate)); - GetSharpnessCaps = (GetSharpnessCapsDelegate)GetDelegate("GetSharpnessCaps", typeof(GetSharpnessCapsDelegate)); - GetSharpnessSettings = (GetSharpnessSettingsDelegate)GetDelegate("GetSharpnessSettings", typeof(GetSharpnessSettingsDelegate)); - SetSharpnessSettings = (SetSharpnessSettingsDelegate)GetDelegate("SetSharpnessSettings", typeof(SetSharpnessSettingsDelegate)); - GetTelemetryData = (GetTelemetryDataDelegate)GetDelegate("GetTelemetryData", typeof(GetTelemetryDataDelegate)); - } - catch - { - status = IGCLStatus.DLL_INITIALIZE_ERROR; - } - } + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; - if (status != IGCLStatus.NO_ERROR) - return false; + // Call Init and check the result + Result = InitializeIgcl(); + return Result == ctl_result_t.CTL_RESULT_SUCCESS; + } - ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + return false; + } - // Call Init and check the result - Result = InitializeIgcl(); - return Result == ctl_result_t.CTL_RESULT_SUCCESS; + public static void Terminate() + { + if (status == IGCLStatus.DLL_INITIALIZE_SUCCESS) + { + CloseIgcl(); + } } public static int GetDeviceIdx(string deviceName) { - // test - Terminate(); - Initialize(); - ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; uint adapterCount = 0; @@ -535,16 +566,6 @@ public static int GetDeviceIdx(string deviceName) return -1; } - public static void Terminate() - { - if (pDll != IntPtr.Zero) - { - CloseIgcl(); - FreeLibrary(pDll); - pDll = IntPtr.Zero; - } - } - internal static bool HasGPUScalingSupport(nint deviceIdx, uint displayIdx) { ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; diff --git a/HandheldCompanion/Managers/ControllerManager.cs b/HandheldCompanion/Managers/ControllerManager.cs index 3bdd84f09..478b16eef 100644 --- a/HandheldCompanion/Managers/ControllerManager.cs +++ b/HandheldCompanion/Managers/ControllerManager.cs @@ -39,10 +39,8 @@ public static class ControllerManager private static bool watchdogThreadRunning; private static bool ControllerManagement; - private static bool ControllerManagementSuccess = false; private static int ControllerManagementAttempts = 0; private const int ControllerManagementMaxAttempts = 3; - public static bool ControllerManagerIsBusy = false; private static readonly XInputController? emptyXInput = new(); private static readonly DS4Controller? emptyDS4 = new(); @@ -53,8 +51,19 @@ public static class ControllerManager private static bool ControllerMuted; private static SensorFamily sensorSelection = SensorFamily.None; + private static object targetLock = new object(); + public static ControllerManagerStatus managerStatus = ControllerManagerStatus.Pending; + public static bool IsInitialized; + public enum ControllerManagerStatus + { + Pending = 0, + Busy = 1, + Succeeded = 2, + Failed = 3, + } + static ControllerManager() { watchdogThread = new Thread(watchdogThreadLoop); @@ -262,6 +271,8 @@ private static void SettingsManager_SettingValueChanged(string name, object valu watchdogThread.Join(); watchdogThread = null; } + + UpdateStatus(ControllerManagerStatus.Failed); } break; } @@ -655,11 +666,8 @@ private static void watchdogThreadLoop(object? obj) if (deviceInstanceIds is not null && deviceInstanceIds.Count != 0) ResumeControllers(); - ControllerManagementSuccess = false; + UpdateStatus(ControllerManagerStatus.Failed); ControllerManagementAttempts = 0; - ControllerManagerIsBusy = false; - - Working?.Invoke(2); // suspend watchdog if (watchdogThread is not null) @@ -673,9 +681,7 @@ private static void watchdogThreadLoop(object? obj) } else { - Working?.Invoke(0); - ControllerManagementSuccess = false; - ControllerManagerIsBusy = true; + UpdateStatus(ControllerManagerStatus.Busy); bool HasBusyWireless = false; bool HasCyclingController = false; @@ -728,13 +734,9 @@ private static void watchdogThreadLoop(object? obj) ResumeControllers(); // give us one extra loop to make sure we're good - if (!ControllerManagementSuccess) - { - ControllerManagementSuccess = true; - Working?.Invoke(1); - } - else - ControllerManagementAttempts = 0; + if (managerStatus != ControllerManagerStatus.Succeeded) + UpdateStatus(ControllerManagerStatus.Succeeded); + ControllerManagementAttempts = 0; } } } @@ -744,6 +746,12 @@ private static void watchdogThreadLoop(object? obj) } } + private static void UpdateStatus(ControllerManagerStatus status) + { + managerStatus = status; + StatusChanged?.Invoke(status); + } + private static async void XUsbDeviceArrived(PnPDetails details, DeviceEventArgs obj) { Controllers.TryGetValue(details.baseContainerDeviceInstanceId, out IController controller); @@ -876,8 +884,6 @@ private static async void XUsbDeviceRemoved(PnPDetails details, DeviceEventArgs ControllerUnplugged?.Invoke(controller, IsPowerCycling, WasTarget); } - private static object targetLock = new object(); - private static void ClearTargetController() { lock (targetLock) @@ -1158,8 +1164,8 @@ internal static IController GetEmulatedController() public static event InputsUpdatedEventHandler InputsUpdated; public delegate void InputsUpdatedEventHandler(ControllerState Inputs); - public static event WorkingEventHandler Working; - public delegate void WorkingEventHandler(int status); + public static event StatusChangedEventHandler StatusChanged; + public delegate void StatusChangedEventHandler(ControllerManagerStatus status); public static event InitializedEventHandler Initialized; public delegate void InitializedEventHandler(); diff --git a/HandheldCompanion/Managers/GPUManager.cs b/HandheldCompanion/Managers/GPUManager.cs index de6bc8b9b..d244b6492 100644 --- a/HandheldCompanion/Managers/GPUManager.cs +++ b/HandheldCompanion/Managers/GPUManager.cs @@ -13,34 +13,23 @@ namespace HandheldCompanion.Managers public static class GPUManager { #region events - public static event InitializedEventHandler Initialized; + public static event InitializedEventHandler? Initialized; public delegate void InitializedEventHandler(bool HasIGCL, bool HasADLX); - public static event HookedEventHandler Hooked; + public static event HookedEventHandler? Hooked; public delegate void HookedEventHandler(GPU GPU); - public static event UnhookedEventHandler Unhooked; + public static event UnhookedEventHandler? Unhooked; public delegate void UnhookedEventHandler(GPU GPU); #endregion public static bool IsInitialized; - public static bool HasIGCL; - public static bool HasADLX; + public static bool IsLoaded_IGCL; + public static bool IsLoaded_ADLX; private static GPU currentGPU = null; private static ConcurrentDictionary DisplayGPU = new(); - static GPUManager() - { - // manage events - ProfileManager.Applied += ProfileManager_Applied; - ProfileManager.Discarded += ProfileManager_Discarded; - ProfileManager.Updated += ProfileManager_Updated; - DeviceManager.DisplayAdapterArrived += DeviceManager_DisplayAdapterArrived; - DeviceManager.DisplayAdapterRemoved += DeviceManager_DisplayAdapterRemoved; - MultimediaManager.PrimaryScreenChanged += MultimediaManager_PrimaryScreenChanged; - } - private static void GPUConnect(GPU GPU) { // update current GPU @@ -60,10 +49,10 @@ private static void GPUConnect(GPU GPU) } if (GPU.IsInitialized) - { GPU.Start(); + + if (IsInitialized && GPU.IsInitialized) Hooked?.Invoke(GPU); - } } private static void GPUDisconnect(GPU gpu) @@ -113,30 +102,33 @@ private static async void MultimediaManager_PrimaryScreenChanged(DesktopScreen s private static async void DeviceManager_DisplayAdapterArrived(AdapterInformation adapterInformation) { - GPU currentGPU = null; + while (!IsInitialized) + await Task.Delay(250); + + GPU newGPU = null; if (adapterInformation.Details.Description.Contains("Advanced Micro Devices") || adapterInformation.Details.Description.Contains("AMD")) { - currentGPU = new AMDGPU(adapterInformation); + newGPU = new AMDGPU(adapterInformation); } else if (adapterInformation.Details.Description.Contains("Intel")) { - currentGPU = new IntelGPU(adapterInformation); + newGPU = new IntelGPU(adapterInformation); } - if (currentGPU is null) + if (newGPU is null) { LogManager.LogError("Unsupported DisplayAdapter: {0}, VendorID:{1}, DeviceId:{2}", adapterInformation.Details.Description, adapterInformation.Details.VendorId, adapterInformation.Details.DeviceId); return; } - if (!currentGPU.IsInitialized) + if (!newGPU.IsInitialized) { LogManager.LogError("Failed to initialize DisplayAdapter: {0}, VendorID:{1}, DeviceId:{2}", adapterInformation.Details.Description, adapterInformation.Details.VendorId, adapterInformation.Details.DeviceId); return; } - DisplayGPU.TryAdd(adapterInformation, currentGPU); + DisplayGPU.TryAdd(adapterInformation, newGPU); } private static void DeviceManager_DisplayAdapterRemoved(AdapterInformation adapterInformation) @@ -155,6 +147,9 @@ public static GPU GetCurrent() private static void CurrentGPU_RSRStateChanged(bool Supported, bool Enabled, int Sharpness) { + if (!IsInitialized) + return; + // todo: use ProfileMager events Profile profile = ProfileManager.GetCurrent(); AMDGPU amdGPU = (AMDGPU)currentGPU; @@ -167,6 +162,9 @@ private static void CurrentGPU_RSRStateChanged(bool Supported, bool Enabled, int private static void CurrentGPU_IntegerScalingChanged(bool Supported, bool Enabled) { + if (!IsInitialized) + return; + // todo: use ProfileMager events Profile profile = ProfileManager.GetCurrent(); @@ -176,6 +174,9 @@ private static void CurrentGPU_IntegerScalingChanged(bool Supported, bool Enable private static void CurrentGPU_GPUScalingChanged(bool Supported, bool Enabled, int Mode) { + if (!IsInitialized) + return; + // todo: use ProfileMager events Profile profile = ProfileManager.GetCurrent(); @@ -187,6 +188,9 @@ private static void CurrentGPU_GPUScalingChanged(bool Supported, bool Enabled, i private static void CurrentGPU_ImageSharpeningChanged(bool Enabled, int Sharpness) { + if (!IsInitialized) + return; + // todo: use ProfileMager events Profile profile = ProfileManager.GetCurrent(); @@ -198,8 +202,11 @@ private static void CurrentGPU_ImageSharpeningChanged(bool Enabled, int Sharpnes public static void Start() { - HasIGCL = IGCLBackend.Initialize(); - HasADLX = ADLXBackend.IntializeAdlx(); + if (IsInitialized) + return; + + IsLoaded_IGCL = IGCLBackend.Initialize(); + IsLoaded_ADLX = ADLXBackend.IntializeAdlx(); // todo: check if usefull on resume // it could be DeviceManager_DisplayAdapterArrived is called already, making this redundant @@ -207,37 +214,58 @@ public static void Start() currentGPU.Start(); IsInitialized = true; - Initialized?.Invoke(HasIGCL, HasADLX); + Initialized?.Invoke(IsLoaded_IGCL, IsLoaded_ADLX); + + // manage events + ProfileManager.Applied += ProfileManager_Applied; + ProfileManager.Discarded += ProfileManager_Discarded; + ProfileManager.Updated += ProfileManager_Updated; + DeviceManager.DisplayAdapterArrived += DeviceManager_DisplayAdapterArrived; + DeviceManager.DisplayAdapterRemoved += DeviceManager_DisplayAdapterRemoved; + MultimediaManager.PrimaryScreenChanged += MultimediaManager_PrimaryScreenChanged; LogManager.LogInformation("{0} has started", "GPUManager"); } - public static async void Stop() + public static void Stop() { if (!IsInitialized) return; - foreach (GPU gpu in DisplayGPU.Values) - gpu.Stop(); + IsInitialized = false; - // wait until all GPUs are ready - while (DisplayGPU.Values.Any(gpu => gpu.IsBusy)) - await Task.Delay(100); + // manage events + ProfileManager.Applied -= ProfileManager_Applied; + ProfileManager.Discarded -= ProfileManager_Discarded; + ProfileManager.Updated -= ProfileManager_Updated; + DeviceManager.DisplayAdapterArrived -= DeviceManager_DisplayAdapterArrived; + DeviceManager.DisplayAdapterRemoved -= DeviceManager_DisplayAdapterRemoved; + MultimediaManager.PrimaryScreenChanged -= MultimediaManager_PrimaryScreenChanged; - if (HasIGCL) - IGCLBackend.Terminate(); + foreach (GPU gpu in DisplayGPU.Values) + gpu.Stop(); - if (HasADLX) - ADLXBackend.CloseAdlx(); + lock (GPU.functionLock) + { + if (IsLoaded_IGCL) + { + IGCLBackend.Terminate(); + IsLoaded_IGCL = false; + } - IsInitialized = false; + if (IsLoaded_ADLX) + { + ADLXBackend.CloseAdlx(); + IsLoaded_ADLX = false; + } + } LogManager.LogInformation("{0} has stopped", "GPUManager"); } private static void ProfileManager_Applied(Profile profile, UpdateSource source) { - if (currentGPU is null) + if (!IsInitialized || currentGPU is null) return; try @@ -304,7 +332,7 @@ private static void ProfileManager_Applied(Profile profile, UpdateSource source) private static void ProfileManager_Discarded(Profile profile) { - if (currentGPU is null) + if (!IsInitialized || currentGPU is null) return; try diff --git a/HandheldCompanion/Managers/MultimediaManager.cs b/HandheldCompanion/Managers/MultimediaManager.cs index 780483fbe..dbfc32b43 100644 --- a/HandheldCompanion/Managers/MultimediaManager.cs +++ b/HandheldCompanion/Managers/MultimediaManager.cs @@ -1,585 +1,585 @@ -using HandheldCompanion.Managers.Desktop; -using Microsoft.Win32; -using NAudio.CoreAudioApi; -using NAudio.CoreAudioApi.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Management; -using System.Media; -using System.Runtime.InteropServices; -using System.Windows.Forms; -using WindowsDisplayAPI; -using WindowsDisplayAPI.DisplayConfig; - -namespace HandheldCompanion.Managers; - -public static class MultimediaManager -{ - private static DesktopScreen desktopScreen; - private static ScreenRotation screenOrientation; - - private static readonly MMDeviceEnumerator DevEnum; - private static MMDevice multimediaDevice; - private static readonly MMDeviceNotificationClient notificationClient; - - private static readonly ManagementEventWatcher BrightnessWatcher; - private static readonly ManagementScope Scope; - - private static bool VolumeSupport; - private static readonly bool BrightnessSupport; - - public static bool IsInitialized; - - static MultimediaManager() - { - // setup the multimedia device and get current volume value - notificationClient = new MMDeviceNotificationClient(); - DevEnum = new MMDeviceEnumerator(); - DevEnum.RegisterEndpointNotificationCallback(notificationClient); - SetDefaultAudioEndPoint(); - - // get current brightness value - Scope = new ManagementScope(@"\\.\root\wmi"); - Scope.Connect(); - - // creating the watcher - BrightnessWatcher = new ManagementEventWatcher(Scope, new EventQuery("Select * From WmiMonitorBrightnessEvent")); - BrightnessWatcher.EventArrived += onWMIEvent; - - // check if we have control over brightness - BrightnessSupport = GetBrightness() != -1; - - // manage events - SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; - SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; - HotkeysManager.CommandExecuted += HotkeysManager_CommandExecuted; - } - - private static void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data) - { - VolumeNotification?.Invoke(data.MasterVolume * 100.0f); - } - - private static void SetDefaultAudioEndPoint() - { - try - { - if (multimediaDevice is not null && multimediaDevice.AudioEndpointVolume is not null) - { - VolumeSupport = false; - multimediaDevice.AudioEndpointVolume.OnVolumeNotification -= AudioEndpointVolume_OnVolumeNotification; - } - - multimediaDevice = DevEnum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - - if (multimediaDevice is not null && multimediaDevice.AudioEndpointVolume is not null) - { - VolumeSupport = true; - multimediaDevice.AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification; - } - - // do this even when no device found, to set to 0 - VolumeNotification?.Invoke((float)GetVolume()); - } - catch (Exception) - { - LogManager.LogError("No AudioEndpoint available"); - } - } - - private static void SettingsManager_SettingValueChanged(string name, object value) - { - switch (name) - { - case "NativeDisplayOrientation": - { - ScreenRotation.Rotations nativeOrientation = (ScreenRotation.Rotations)Convert.ToInt32(value); - - if (!IsInitialized) - return; - - ScreenRotation.Rotations oldOrientation = screenOrientation.rotation; - screenOrientation = new ScreenRotation(screenOrientation.rotationUnnormalized, nativeOrientation); - - // Though the real orientation didn't change, raise event because the interpretation of it changed - if (oldOrientation != screenOrientation.rotation) - DisplayOrientationChanged?.Invoke(screenOrientation); - } - break; - } - } - - private static void HotkeysManager_CommandExecuted(string listener) - { - switch (listener) - { - case "increaseBrightness": - { - int stepRoundDn = (int)Math.Floor(GetBrightness() / 5.0d); - int brightness = stepRoundDn * 5 + 5; - SetBrightness(brightness); - } - break; - case "decreaseBrightness": - { - int stepRoundUp = (int)Math.Ceiling(GetBrightness() / 5.0d); - int brightness = stepRoundUp * 5 - 5; - SetBrightness(brightness); - } - break; - case "increaseVolume": - { - int stepRoundDn = (int)Math.Floor(Math.Round(GetVolume() / 5.0d, 2)); - int volume = stepRoundDn * 5 + 5; - SetVolume(volume); - } - break; - case "decreaseVolume": - { - int stepRoundUp = (int)Math.Ceiling(Math.Round(GetVolume() / 5.0d, 2)); - int volume = stepRoundUp * 5 - 5; - SetVolume(volume); - } - break; - } - } - - private static void onWMIEvent(object sender, EventArrivedEventArgs e) - { - int brightness = Convert.ToInt32(e.NewEvent.Properties["Brightness"].Value); - BrightnessNotification?.Invoke(brightness); - } - - public static string GetDisplayFriendlyName(string DeviceName) - { - string friendlyName = string.Empty; - - Display? PrimaryDisplay = Display.GetDisplays().Where(display => display.DisplayName.Equals(DeviceName)).FirstOrDefault(); - if (PrimaryDisplay is not null) - { - string DevicePath = PrimaryDisplay.DevicePath; - PathDisplayTarget? PrimaryTarget = GetDisplayTarget(DevicePath); - if (PrimaryTarget is not null) - friendlyName = PrimaryTarget.FriendlyName; - } - - return friendlyName; - } - - public static string GetDisplayPath(string DeviceName) - { - string DevicePath = string.Empty; - - Display? PrimaryDisplay = Display.GetDisplays().Where(display => display.DisplayName.Equals(DeviceName)).FirstOrDefault(); - if (PrimaryDisplay is not null) - DevicePath = PrimaryDisplay.DevicePath; - - return DevicePath; - } - - private static PathDisplayTarget? GetDisplayTarget(string DevicePath) - { - PathDisplayTarget PrimaryTarget; - PrimaryTarget = PathDisplayTarget.GetDisplayTargets().Where(target => target.DevicePath.Equals(DevicePath)).FirstOrDefault(); - return PrimaryTarget; - } - - private static void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) - { - // get primary screen - Screen PrimaryScreen = Screen.PrimaryScreen; - - if (desktopScreen is null || desktopScreen.PrimaryScreen.DeviceName != PrimaryScreen.DeviceName) - { - // update current desktop screen - desktopScreen = new DesktopScreen(PrimaryScreen); - desktopScreen.devMode = GetDisplay(PrimaryScreen.DeviceName); - desktopScreen.FriendlyName = GetDisplayFriendlyName(PrimaryScreen.DeviceName); - desktopScreen.DevicePath = GetDisplayPath(PrimaryScreen.DeviceName); - - // pull resolutions details - List resolutions = GetResolutions(desktopScreen.PrimaryScreen.DeviceName); - foreach (DisplayDevice mode in resolutions) - { - ScreenResolution res = new ScreenResolution(mode.dmPelsWidth, mode.dmPelsHeight, mode.dmBitsPerPel); - - List frequencies = resolutions - .Where(a => a.dmPelsWidth == mode.dmPelsWidth && a.dmPelsHeight == mode.dmPelsHeight) - .Select(b => b.dmDisplayFrequency).Distinct().ToList(); - - foreach (int frequency in frequencies) - res.Frequencies.Add(frequency, frequency); - - if (!desktopScreen.HasResolution(res)) - desktopScreen.screenResolutions.Add(res); - } - - // sort resolutions - desktopScreen.SortResolutions(); - - // raise event - PrimaryScreenChanged?.Invoke(desktopScreen); - } - else - { - // update current desktop resolution - desktopScreen.devMode = GetDisplay(desktopScreen.PrimaryScreen.DeviceName); - } - - ScreenRotation.Rotations oldOrientation = screenOrientation.rotation; - - if (!IsInitialized) - { - ScreenRotation.Rotations nativeScreenRotation = (ScreenRotation.Rotations)SettingsManager.GetInt("NativeDisplayOrientation"); - screenOrientation = new ScreenRotation((ScreenRotation.Rotations)desktopScreen.devMode.dmDisplayOrientation, nativeScreenRotation); - oldOrientation = ScreenRotation.Rotations.UNSET; - - if (nativeScreenRotation == ScreenRotation.Rotations.UNSET) - SettingsManager.SetProperty("NativeDisplayOrientation", (int)screenOrientation.rotationNativeBase, true); - } - else - { - screenOrientation = new ScreenRotation((ScreenRotation.Rotations)desktopScreen.devMode.dmDisplayOrientation, screenOrientation.rotationNativeBase); - } - - // raise event - ScreenResolution screenResolution = desktopScreen.GetResolution(desktopScreen.devMode.dmPelsWidth, desktopScreen.devMode.dmPelsHeight); - if (screenResolution is not null) - DisplaySettingsChanged?.Invoke(desktopScreen, screenResolution); - - // raise event - if (oldOrientation != screenOrientation.rotation) - DisplayOrientationChanged?.Invoke(screenOrientation); - } - - public static DesktopScreen GetDesktopScreen() - { - return desktopScreen; - } - - public static ScreenRotation GetScreenOrientation() - { - return screenOrientation; - } - - public static void Start() - { - // start brightness watcher - BrightnessWatcher.Start(); - - // force trigger events - SystemEvents_DisplaySettingsChanged(null, null); - - // get native resolution - ScreenResolution nativeResolution = desktopScreen.screenResolutions.First(); - - // get integer scaling dividers - int idx = 1; - - while (true) - { - int height = nativeResolution.Height / idx; - ScreenResolution? dividedRes = desktopScreen.screenResolutions.FirstOrDefault(r => r.Height == height); - if (dividedRes is null) - break; - - desktopScreen.screenDividers.Add(new(idx, dividedRes)); - idx++; - } - - IsInitialized = true; - Initialized?.Invoke(); - - LogManager.LogInformation("{0} has started", "SystemManager"); - } - - public static void Stop() - { - if (!IsInitialized) - return; - - // stop brightness watcher - BrightnessWatcher.Stop(); - - DevEnum.UnregisterEndpointNotificationCallback(notificationClient); - - IsInitialized = false; - - LogManager.LogInformation("{0} has stopped", "SystemManager"); - } - - public static bool SetResolution(int width, int height, int displayFrequency) - { - if (!IsInitialized) - return false; - - bool ret = false; - DisplayDevice dm = new DisplayDevice(); - dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); - dm.dmPelsWidth = width; - dm.dmPelsHeight = height; - dm.dmDisplayFrequency = displayFrequency; - dm.dmFields = DisplayDevice.DM_PELSWIDTH | DisplayDevice.DM_PELSHEIGHT | DisplayDevice.DM_DISPLAYFREQUENCY; - - long RetVal = ChangeDisplaySettings(ref dm, CDS_TEST); - if (RetVal == 0) - { - RetVal = ChangeDisplaySettings(ref dm, 0); - ret = true; - } - - return ret; - } - - public static bool SetResolution(int width, int height, int displayFrequency, int bitsPerPel) - { - if (!IsInitialized) - return false; - - bool ret = false; - DisplayDevice dm = new DisplayDevice(); - dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); - dm.dmPelsWidth = width; - dm.dmPelsHeight = height; - dm.dmDisplayFrequency = displayFrequency; - dm.dmBitsPerPel = bitsPerPel; - dm.dmFields = DisplayDevice.DM_PELSWIDTH | DisplayDevice.DM_PELSHEIGHT | DisplayDevice.DM_DISPLAYFREQUENCY; - - long RetVal = ChangeDisplaySettings(ref dm, CDS_TEST); - if (RetVal == 0) - { - RetVal = ChangeDisplaySettings(ref dm, 0); - ret = true; - } - - return ret; - } - - public static DisplayDevice GetDisplay(string DeviceName) - { - DisplayDevice dm = new DisplayDevice(); - dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); - EnumDisplaySettings(DeviceName, -1, ref dm); - return dm; - } - - public static List GetResolutions(string DeviceName) - { - List allMode = new List(); - DisplayDevice dm = new DisplayDevice(); - dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); - int index = 0; - while (EnumDisplaySettings(DeviceName, index, ref dm)) - { - allMode.Add(dm); - index++; - } - - return allMode; - } - - public static void PlayWindowsMedia(string file) - { - string path = Path.Combine(@"c:\Windows\Media\", file); - if (File.Exists(path)) - new SoundPlayer(path).Play(); - } - - public static bool HasVolumeSupport() - { - return VolumeSupport; - } - - public static void SetVolume(double volume) - { - if (!VolumeSupport) - return; - - multimediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = (float)(volume / 100.0d); - } - - public static double GetVolume() - { - if (!VolumeSupport) - return 0.0d; - - return multimediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100.0d; - } - - public static bool HasBrightnessSupport() - { - return BrightnessSupport; - } - - public static void SetBrightness(double brightness) - { - if (!BrightnessSupport) - return; - - try - { - using (ManagementClass mclass = new ManagementClass("WmiMonitorBrightnessMethods")) - { - mclass.Scope = new ManagementScope(@"\\.\root\wmi"); - using (ManagementObjectCollection instances = mclass.GetInstances()) - { - foreach (ManagementObject instance in instances) - { - object[] args = { 1, brightness }; - instance.InvokeMethod("WmiSetBrightness", args); - } - } - } - } - catch - { - } - } - - public static short GetBrightness() - { - try - { - using (ManagementClass mclass = new ManagementClass("WmiMonitorBrightness")) - { - mclass.Scope = new ManagementScope(@"\\.\root\wmi"); - using (ManagementObjectCollection instances = mclass.GetInstances()) - { - foreach (ManagementObject instance in instances) - return (byte)instance.GetPropertyValue("CurrentBrightness"); - } - } - - return 0; - } - catch - { - } - - return -1; - } - - private class MMDeviceNotificationClient : IMMNotificationClient - { - public void OnDefaultDeviceChanged(DataFlow flow, Role role, string defaultDeviceId) - { - SetDefaultAudioEndPoint(); - } - - public void OnDeviceAdded(string deviceId) - { - } - - public void OnDeviceRemoved(string deviceId) - { - } - - public void OnDeviceStateChanged(string deviceId, DeviceState newState) - { - } - - public void OnPropertyValueChanged(string deviceId, PropertyKey key) - { - } - } - - #region imports - - public enum DMDO - { - DEFAULT = 0, - D90 = 1, - D180 = 2, - D270 = 3 - } - - public const int CDS_UPDATEREGISTRY = 0x01; - public const int CDS_TEST = 0x02; - public const int DISP_CHANGE_SUCCESSFUL = 0; - public const int DISP_CHANGE_RESTART = 1; - public const int DISP_CHANGE_FAILED = -1; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct DisplayDevice - { - public const int DM_DISPLAYFREQUENCY = 0x400000; - public const int DM_PELSWIDTH = 0x80000; - public const int DM_PELSHEIGHT = 0x100000; - private const int CCHDEVICENAME = 32; - private const int CCHFORMNAME = 32; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] - public string dmDeviceName; - - public short dmSpecVersion; - public short dmDriverVersion; - public short dmSize; - public short dmDriverExtra; - public int dmFields; - - public int dmPositionX; - public int dmPositionY; - public DMDO dmDisplayOrientation; - public int dmDisplayFixedOutput; - - public short dmColor; - public short dmDuplex; - public short dmYResolution; - public short dmTTOption; - public short dmCollate; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)] - public string dmFormName; - - public short dmLogPixels; - public int dmBitsPerPel; - public int dmPelsWidth; - public int dmPelsHeight; - public int dmDisplayFlags; - public int dmDisplayFrequency; - public int dmICMMethod; - public int dmICMIntent; - public int dmMediaType; - public int dmDitherType; - public int dmReserved1; - public int dmReserved2; - public int dmPanningWidth; - public int dmPanningHeight; - - public override string ToString() - { - return $"{dmPelsWidth}x{dmPelsHeight}, {dmDisplayFrequency}, {dmBitsPerPel}"; - } - } - - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern int ChangeDisplaySettings([In] ref DisplayDevice lpDevMode, int dwFlags); - - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DisplayDevice lpDevMode); - #endregion - - #region events - - public static event DisplaySettingsChangedEventHandler DisplaySettingsChanged; - public delegate void DisplaySettingsChangedEventHandler(DesktopScreen screen, ScreenResolution resolution); - - public static event PrimaryScreenChangedEventHandler PrimaryScreenChanged; - public delegate void PrimaryScreenChangedEventHandler(DesktopScreen screen); - - public static event DisplayOrientationChangedEventHandler DisplayOrientationChanged; - public delegate void DisplayOrientationChangedEventHandler(ScreenRotation rotation); - - public static event VolumeNotificationEventHandler VolumeNotification; - public delegate void VolumeNotificationEventHandler(float volume); - - public static event BrightnessNotificationEventHandler BrightnessNotification; - public delegate void BrightnessNotificationEventHandler(int brightness); - - public static event InitializedEventHandler Initialized; - public delegate void InitializedEventHandler(); - - #endregion +using HandheldCompanion.Managers.Desktop; +using Microsoft.Win32; +using NAudio.CoreAudioApi; +using NAudio.CoreAudioApi.Interfaces; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management; +using System.Media; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using WindowsDisplayAPI; +using WindowsDisplayAPI.DisplayConfig; + +namespace HandheldCompanion.Managers; + +public static class MultimediaManager +{ + private static DesktopScreen desktopScreen; + private static ScreenRotation screenOrientation; + + private static readonly MMDeviceEnumerator DevEnum; + private static MMDevice multimediaDevice; + private static readonly MMDeviceNotificationClient notificationClient; + + private static readonly ManagementEventWatcher BrightnessWatcher; + private static readonly ManagementScope Scope; + + private static bool VolumeSupport; + private static readonly bool BrightnessSupport; + + public static bool IsInitialized; + + static MultimediaManager() + { + // setup the multimedia device and get current volume value + notificationClient = new MMDeviceNotificationClient(); + DevEnum = new MMDeviceEnumerator(); + DevEnum.RegisterEndpointNotificationCallback(notificationClient); + SetDefaultAudioEndPoint(); + + // get current brightness value + Scope = new ManagementScope(@"\\.\root\wmi"); + Scope.Connect(); + + // creating the watcher + BrightnessWatcher = new ManagementEventWatcher(Scope, new EventQuery("Select * From WmiMonitorBrightnessEvent")); + BrightnessWatcher.EventArrived += onWMIEvent; + + // check if we have control over brightness + BrightnessSupport = GetBrightness() != -1; + + // manage events + SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; + SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; + HotkeysManager.CommandExecuted += HotkeysManager_CommandExecuted; + } + + private static void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data) + { + VolumeNotification?.Invoke(data.MasterVolume * 100.0f); + } + + private static void SetDefaultAudioEndPoint() + { + try + { + if (multimediaDevice is not null && multimediaDevice.AudioEndpointVolume is not null) + { + VolumeSupport = false; + multimediaDevice.AudioEndpointVolume.OnVolumeNotification -= AudioEndpointVolume_OnVolumeNotification; + } + + multimediaDevice = DevEnum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + + if (multimediaDevice is not null && multimediaDevice.AudioEndpointVolume is not null) + { + VolumeSupport = true; + multimediaDevice.AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification; + } + + // do this even when no device found, to set to 0 + VolumeNotification?.Invoke((float)GetVolume()); + } + catch (Exception) + { + LogManager.LogError("No AudioEndpoint available"); + } + } + + private static void SettingsManager_SettingValueChanged(string name, object value) + { + switch (name) + { + case "NativeDisplayOrientation": + { + ScreenRotation.Rotations nativeOrientation = (ScreenRotation.Rotations)Convert.ToInt32(value); + + if (!IsInitialized) + return; + + ScreenRotation.Rotations oldOrientation = screenOrientation.rotation; + screenOrientation = new ScreenRotation(screenOrientation.rotationUnnormalized, nativeOrientation); + + // Though the real orientation didn't change, raise event because the interpretation of it changed + if (oldOrientation != screenOrientation.rotation) + DisplayOrientationChanged?.Invoke(screenOrientation); + } + break; + } + } + + private static void HotkeysManager_CommandExecuted(string listener) + { + switch (listener) + { + case "increaseBrightness": + { + int stepRoundDn = (int)Math.Floor(GetBrightness() / 5.0d); + int brightness = stepRoundDn * 5 + 5; + SetBrightness(brightness); + } + break; + case "decreaseBrightness": + { + int stepRoundUp = (int)Math.Ceiling(GetBrightness() / 5.0d); + int brightness = stepRoundUp * 5 - 5; + SetBrightness(brightness); + } + break; + case "increaseVolume": + { + int stepRoundDn = (int)Math.Floor(Math.Round(GetVolume() / 5.0d, 2)); + int volume = stepRoundDn * 5 + 5; + SetVolume(volume); + } + break; + case "decreaseVolume": + { + int stepRoundUp = (int)Math.Ceiling(Math.Round(GetVolume() / 5.0d, 2)); + int volume = stepRoundUp * 5 - 5; + SetVolume(volume); + } + break; + } + } + + private static void onWMIEvent(object sender, EventArrivedEventArgs e) + { + int brightness = Convert.ToInt32(e.NewEvent.Properties["Brightness"].Value); + BrightnessNotification?.Invoke(brightness); + } + + public static string GetDisplayFriendlyName(string DeviceName) + { + string friendlyName = string.Empty; + + Display? PrimaryDisplay = Display.GetDisplays().Where(display => display.DisplayName.Equals(DeviceName)).FirstOrDefault(); + if (PrimaryDisplay is not null) + { + string DevicePath = PrimaryDisplay.DevicePath; + PathDisplayTarget? PrimaryTarget = GetDisplayTarget(DevicePath); + if (PrimaryTarget is not null) + friendlyName = PrimaryTarget.FriendlyName; + } + + return friendlyName; + } + + public static string GetDisplayPath(string DeviceName) + { + string DevicePath = string.Empty; + + Display? PrimaryDisplay = Display.GetDisplays().Where(display => display.DisplayName.Equals(DeviceName)).FirstOrDefault(); + if (PrimaryDisplay is not null) + DevicePath = PrimaryDisplay.DevicePath; + + return DevicePath; + } + + private static PathDisplayTarget? GetDisplayTarget(string DevicePath) + { + PathDisplayTarget PrimaryTarget; + PrimaryTarget = PathDisplayTarget.GetDisplayTargets().Where(target => target.DevicePath.Equals(DevicePath)).FirstOrDefault(); + return PrimaryTarget; + } + + private static void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) + { + // get primary screen + Screen PrimaryScreen = Screen.PrimaryScreen; + + if (desktopScreen is null || desktopScreen.PrimaryScreen.DeviceName != PrimaryScreen.DeviceName) + { + // update current desktop screen + desktopScreen = new DesktopScreen(PrimaryScreen); + desktopScreen.devMode = GetDisplay(PrimaryScreen.DeviceName); + desktopScreen.FriendlyName = GetDisplayFriendlyName(PrimaryScreen.DeviceName); + desktopScreen.DevicePath = GetDisplayPath(PrimaryScreen.DeviceName); + + // pull resolutions details + List resolutions = GetResolutions(desktopScreen.PrimaryScreen.DeviceName); + foreach (DisplayDevice mode in resolutions) + { + ScreenResolution res = new ScreenResolution(mode.dmPelsWidth, mode.dmPelsHeight, mode.dmBitsPerPel); + + List frequencies = resolutions + .Where(a => a.dmPelsWidth == mode.dmPelsWidth && a.dmPelsHeight == mode.dmPelsHeight) + .Select(b => b.dmDisplayFrequency).Distinct().ToList(); + + foreach (int frequency in frequencies) + res.Frequencies.Add(frequency, frequency); + + if (!desktopScreen.HasResolution(res)) + desktopScreen.screenResolutions.Add(res); + } + + // sort resolutions + desktopScreen.SortResolutions(); + + // raise event + PrimaryScreenChanged?.Invoke(desktopScreen); + } + else + { + // update current desktop resolution + desktopScreen.devMode = GetDisplay(desktopScreen.PrimaryScreen.DeviceName); + } + + ScreenRotation.Rotations oldOrientation = screenOrientation.rotation; + + if (!IsInitialized) + { + ScreenRotation.Rotations nativeScreenRotation = (ScreenRotation.Rotations)SettingsManager.GetInt("NativeDisplayOrientation"); + screenOrientation = new ScreenRotation((ScreenRotation.Rotations)desktopScreen.devMode.dmDisplayOrientation, nativeScreenRotation); + oldOrientation = ScreenRotation.Rotations.UNSET; + + if (nativeScreenRotation == ScreenRotation.Rotations.UNSET) + SettingsManager.SetProperty("NativeDisplayOrientation", (int)screenOrientation.rotationNativeBase, true); + } + else + { + screenOrientation = new ScreenRotation((ScreenRotation.Rotations)desktopScreen.devMode.dmDisplayOrientation, screenOrientation.rotationNativeBase); + } + + // raise event + ScreenResolution screenResolution = desktopScreen.GetResolution(desktopScreen.devMode.dmPelsWidth, desktopScreen.devMode.dmPelsHeight); + if (screenResolution is not null) + DisplaySettingsChanged?.Invoke(desktopScreen, screenResolution); + + // raise event + if (oldOrientation != screenOrientation.rotation) + DisplayOrientationChanged?.Invoke(screenOrientation); + } + + public static DesktopScreen GetDesktopScreen() + { + return desktopScreen; + } + + public static ScreenRotation GetScreenOrientation() + { + return screenOrientation; + } + + public static void Start() + { + // start brightness watcher + BrightnessWatcher.Start(); + + // force trigger events + SystemEvents_DisplaySettingsChanged(null, null); + + // get native resolution + ScreenResolution nativeResolution = desktopScreen.screenResolutions.First(); + + // get integer scaling dividers + int idx = 1; + + while (true) + { + int height = nativeResolution.Height / idx; + ScreenResolution? dividedRes = desktopScreen.screenResolutions.FirstOrDefault(r => r.Height == height); + if (dividedRes is null) + break; + + desktopScreen.screenDividers.Add(new(idx, dividedRes)); + idx++; + } + + IsInitialized = true; + Initialized?.Invoke(); + + LogManager.LogInformation("{0} has started", "SystemManager"); + } + + public static void Stop() + { + if (!IsInitialized) + return; + + // stop brightness watcher + BrightnessWatcher.Stop(); + + DevEnum.UnregisterEndpointNotificationCallback(notificationClient); + + IsInitialized = false; + + LogManager.LogInformation("{0} has stopped", "SystemManager"); + } + + public static bool SetResolution(int width, int height, int displayFrequency) + { + if (!IsInitialized) + return false; + + bool ret = false; + DisplayDevice dm = new DisplayDevice(); + dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + dm.dmDisplayFrequency = displayFrequency; + dm.dmFields = DisplayDevice.DM_PELSWIDTH | DisplayDevice.DM_PELSHEIGHT | DisplayDevice.DM_DISPLAYFREQUENCY; + + long RetVal = ChangeDisplaySettings(ref dm, CDS_TEST); + if (RetVal == 0) + { + RetVal = ChangeDisplaySettings(ref dm, 0); + ret = true; + } + + return ret; + } + + public static bool SetResolution(int width, int height, int displayFrequency, int bitsPerPel) + { + if (!IsInitialized) + return false; + + bool ret = false; + DisplayDevice dm = new DisplayDevice(); + dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + dm.dmDisplayFrequency = displayFrequency; + dm.dmBitsPerPel = bitsPerPel; + dm.dmFields = DisplayDevice.DM_PELSWIDTH | DisplayDevice.DM_PELSHEIGHT | DisplayDevice.DM_DISPLAYFREQUENCY; + + long RetVal = ChangeDisplaySettings(ref dm, CDS_TEST); + if (RetVal == 0) + { + RetVal = ChangeDisplaySettings(ref dm, 0); + ret = true; + } + + return ret; + } + + public static DisplayDevice GetDisplay(string DeviceName) + { + DisplayDevice dm = new DisplayDevice(); + dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); + EnumDisplaySettings(DeviceName, -1, ref dm); + return dm; + } + + public static List GetResolutions(string DeviceName) + { + List allMode = new List(); + DisplayDevice dm = new DisplayDevice(); + dm.dmSize = (short)Marshal.SizeOf(typeof(DisplayDevice)); + int index = 0; + while (EnumDisplaySettings(DeviceName, index, ref dm)) + { + allMode.Add(dm); + index++; + } + + return allMode; + } + + public static void PlayWindowsMedia(string file) + { + string path = Path.Combine(@"c:\Windows\Media\", file); + if (File.Exists(path)) + new SoundPlayer(path).Play(); + } + + public static bool HasVolumeSupport() + { + return VolumeSupport; + } + + public static void SetVolume(double volume) + { + if (!VolumeSupport) + return; + + multimediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = (float)(volume / 100.0d); + } + + public static double GetVolume() + { + if (!VolumeSupport) + return 0.0d; + + return multimediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100.0d; + } + + public static bool HasBrightnessSupport() + { + return BrightnessSupport; + } + + public static void SetBrightness(double brightness) + { + if (!BrightnessSupport) + return; + + try + { + using (ManagementClass mclass = new ManagementClass("WmiMonitorBrightnessMethods")) + { + mclass.Scope = new ManagementScope(@"\\.\root\wmi"); + using (ManagementObjectCollection instances = mclass.GetInstances()) + { + foreach (ManagementObject instance in instances) + { + object[] args = { 1, brightness }; + instance.InvokeMethod("WmiSetBrightness", args); + } + } + } + } + catch + { + } + } + + public static short GetBrightness() + { + try + { + using (ManagementClass mclass = new ManagementClass("WmiMonitorBrightness")) + { + mclass.Scope = new ManagementScope(@"\\.\root\wmi"); + using (ManagementObjectCollection instances = mclass.GetInstances()) + { + foreach (ManagementObject instance in instances) + return (byte)instance.GetPropertyValue("CurrentBrightness"); + } + } + + return 0; + } + catch + { + } + + return -1; + } + + private class MMDeviceNotificationClient : IMMNotificationClient + { + public void OnDefaultDeviceChanged(DataFlow flow, Role role, string defaultDeviceId) + { + SetDefaultAudioEndPoint(); + } + + public void OnDeviceAdded(string deviceId) + { + } + + public void OnDeviceRemoved(string deviceId) + { + } + + public void OnDeviceStateChanged(string deviceId, DeviceState newState) + { + } + + public void OnPropertyValueChanged(string deviceId, PropertyKey key) + { + } + } + + #region imports + + public enum DMDO + { + DEFAULT = 0, + D90 = 1, + D180 = 2, + D270 = 3 + } + + public const int CDS_UPDATEREGISTRY = 0x01; + public const int CDS_TEST = 0x02; + public const int DISP_CHANGE_SUCCESSFUL = 0; + public const int DISP_CHANGE_RESTART = 1; + public const int DISP_CHANGE_FAILED = -1; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct DisplayDevice + { + public const int DM_DISPLAYFREQUENCY = 0x400000; + public const int DM_PELSWIDTH = 0x80000; + public const int DM_PELSHEIGHT = 0x100000; + private const int CCHDEVICENAME = 32; + private const int CCHFORMNAME = 32; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] + public string dmDeviceName; + + public short dmSpecVersion; + public short dmDriverVersion; + public short dmSize; + public short dmDriverExtra; + public int dmFields; + + public int dmPositionX; + public int dmPositionY; + public DMDO dmDisplayOrientation; + public int dmDisplayFixedOutput; + + public short dmColor; + public short dmDuplex; + public short dmYResolution; + public short dmTTOption; + public short dmCollate; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)] + public string dmFormName; + + public short dmLogPixels; + public int dmBitsPerPel; + public int dmPelsWidth; + public int dmPelsHeight; + public int dmDisplayFlags; + public int dmDisplayFrequency; + public int dmICMMethod; + public int dmICMIntent; + public int dmMediaType; + public int dmDitherType; + public int dmReserved1; + public int dmReserved2; + public int dmPanningWidth; + public int dmPanningHeight; + + public override string ToString() + { + return $"{dmPelsWidth}x{dmPelsHeight}, {dmDisplayFrequency}, {dmBitsPerPel}"; + } + } + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern int ChangeDisplaySettings([In] ref DisplayDevice lpDevMode, int dwFlags); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DisplayDevice lpDevMode); + #endregion + + #region events + + public static event DisplaySettingsChangedEventHandler DisplaySettingsChanged; + public delegate void DisplaySettingsChangedEventHandler(DesktopScreen screen, ScreenResolution resolution); + + public static event PrimaryScreenChangedEventHandler PrimaryScreenChanged; + public delegate void PrimaryScreenChangedEventHandler(DesktopScreen screen); + + public static event DisplayOrientationChangedEventHandler DisplayOrientationChanged; + public delegate void DisplayOrientationChangedEventHandler(ScreenRotation rotation); + + public static event VolumeNotificationEventHandler VolumeNotification; + public delegate void VolumeNotificationEventHandler(float volume); + + public static event BrightnessNotificationEventHandler BrightnessNotification; + public delegate void BrightnessNotificationEventHandler(int brightness); + + public static event InitializedEventHandler Initialized; + public delegate void InitializedEventHandler(); + + #endregion } \ No newline at end of file diff --git a/HandheldCompanion/Managers/ProfileManager.cs b/HandheldCompanion/Managers/ProfileManager.cs index d1e50cee6..bd3d47ce3 100644 --- a/HandheldCompanion/Managers/ProfileManager.cs +++ b/HandheldCompanion/Managers/ProfileManager.cs @@ -1,863 +1,867 @@ -using HandheldCompanion.Controllers; -using HandheldCompanion.Controls; -using HandheldCompanion.Devices; -using HandheldCompanion.Misc; -using HandheldCompanion.Properties; -using HandheldCompanion.Utils; -using HandheldCompanion.Views; -using iNKORE.UI.WPF.Modern.Controls; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using static HandheldCompanion.Utils.XInputPlusUtils; - -namespace HandheldCompanion.Managers; - -public static class ProfileManager -{ - public const string DefaultName = "Default"; - - public static Dictionary profiles = new(StringComparer.InvariantCultureIgnoreCase); - public static List subProfiles = new(); - - private static Profile currentProfile; - - private static string ProfilesPath; - - public static bool IsInitialized; - - static ProfileManager() - { - // initialiaze path(s) - ProfilesPath = Path.Combine(MainWindow.SettingsPath, "profiles"); - if (!Directory.Exists(ProfilesPath)) - Directory.CreateDirectory(ProfilesPath); - - ProcessManager.ForegroundChanged += ProcessManager_ForegroundChanged; - ProcessManager.ProcessStarted += ProcessManager_ProcessStarted; - ProcessManager.ProcessStopped += ProcessManager_ProcessStopped; - - PowerProfileManager.Deleted += PowerProfileManager_Deleted; - - ControllerManager.ControllerPlugged += ControllerManager_ControllerPlugged; - } - - public static FileSystemWatcher profileWatcher { get; set; } - - public static void Start() - { - // monitor profile files - profileWatcher = new FileSystemWatcher - { - Path = ProfilesPath, - EnableRaisingEvents = true, - IncludeSubdirectories = true, - Filter = "*.json", - NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size - }; - profileWatcher.Created += ProfileCreated; - profileWatcher.Deleted += ProfileDeleted; - - // process existing profiles - string[] fileEntries = Directory.GetFiles(ProfilesPath, "*.json", SearchOption.AllDirectories); - foreach (string fileName in fileEntries) - ProcessProfile(fileName, false); - - // check for default profile - if (!HasDefault()) - { - Layout deviceLayout = IDevice.GetCurrent().DefaultLayout.Clone() as Layout; - Profile defaultProfile = new() - { - Name = DefaultName, - Default = true, - Enabled = false, - Layout = deviceLayout, - LayoutTitle = LayoutTemplate.DefaultLayout.Name, - LayoutEnabled = true - }; - - UpdateOrCreateProfile(defaultProfile, UpdateSource.Creation); - } - - IsInitialized = true; - Initialized?.Invoke(); - - LogManager.LogInformation("{0} has started", "ProfileManager"); - } - - public static void Stop() - { - if (!IsInitialized) - return; - - IsInitialized = false; - - profileWatcher.Deleted -= ProfileDeleted; - profileWatcher.Dispose(); - - LogManager.LogInformation("{0} has stopped", "ProfileManager"); - } - - public static bool Contains(Profile profile) - { - foreach (var pr in profiles.Values) - if (pr.Path.Equals(profile.Path, StringComparison.InvariantCultureIgnoreCase)) - return true; - - return false; - } - - public static bool Contains(string fileName) - { - foreach (var pr in profiles.Values) - if (pr.Path.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)) - return true; - - return false; - } - - public static Profile GetProfileFromPath(string path, bool ignoreStatus) - { - // check if favorite sub profile exists for path - Profile profile = subProfiles.FirstOrDefault(pr => pr.Path == path && pr.IsFavoriteSubProfile); - - // get main profile from path instead - if (profile is null) - profile = profiles.Values.FirstOrDefault(a => a.Path.Equals(path, StringComparison.InvariantCultureIgnoreCase)); - - if (profile is null) - { - // otherwise, get profile from executable - string fileName = Path.GetFileName(path); - profile = profiles.Values.FirstOrDefault(a => a.Executable.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); - - if (profile is null) - return GetDefault(); - } - - // ignore profile status (enabled/disabled) - if (ignoreStatus) - return profile; - - return profile.Enabled ? profile : GetDefault(); - } - - public static Profile GetProfileFromGuid(Guid Guid, bool ignoreStatus, bool isSubProfile = false) - { - Profile profile = null; - - if (isSubProfile) - profile = subProfiles.FirstOrDefault(pr => pr.Guid == Guid); - else - profile = profiles.Values.FirstOrDefault(pr => pr.Guid == Guid); - - // get profile from path - if (profile is null) - return GetDefault(); - - // ignore profile status (enabled/disabled) - if (ignoreStatus) - return profile; - - return profile.Enabled ? profile : GetDefault(); - } - - public static Profile[] GetSubProfilesFromPath(string path, bool ignoreStatus) - { - // get subprofile corresponding to path - List filteredSubProfiles = subProfiles.Where(pr => pr.Path == path).ToList(); - return filteredSubProfiles.OrderBy(pr => pr.Name).ToArray(); - } - - public static Profile GetProfileForSubProfile(Profile subProfile) - { - // if passed in profile is main profile - if (!subProfile.IsSubProfile || !profiles.ContainsKey(subProfile.Path)) - return subProfile; - - // get the main profile if it exists/loaded .. else return the profile itself - return profiles[subProfile.Path]; - } - - public static void SetSubProfileAsFavorite(Profile subProfile) - { - // remove favorite from all subprofiles - foreach (var profile in GetSubProfilesFromPath(subProfile.Path, false)) - { - profile.IsFavoriteSubProfile = false; - SerializeProfile(profile); - } - - // check if subProfile is not the main profile itself - if (subProfile.IsSubProfile) - { - subProfile.IsFavoriteSubProfile = true; - SerializeProfile(subProfile); - } - } - - public static void CycleSubProfiles(bool previous = false) - { - if (currentProfile == null) - return; - // called using previousSubProfile/nextSubProfile hotkeys - List subProfilesList = new(); - subProfilesList.Add(GetProfileForSubProfile(currentProfile)); // adds main profile as sub profile - subProfilesList.AddRange(GetSubProfilesFromPath(currentProfile.Path, false).ToList()); // adds all sub profiles - - // if profile does not have sub profiles -> do nothing - if (subProfilesList.Count <= 1) - return; - - // get index of currently applied profile - int currentIndex = subProfilesList.IndexOf(currentProfile); - int newIndex = currentIndex; - - // previous? decrement, next? increment - if (previous) - newIndex -= 1; - else - newIndex += 1; - - // ensure index is within list bounds, wrap if needed - newIndex = (newIndex + subProfilesList.Count) % subProfilesList.Count; - - // if for whatever reason index is out of bound -> return - if (newIndex < 0 || newIndex >= subProfilesList.Count) - return; - - // apply profile - Profile profileToApply = subProfilesList[newIndex]; - UpdateOrCreateProfile(profileToApply); - } - - - private static void ApplyProfile(Profile profile, UpdateSource source = UpdateSource.Background, - bool announce = true) - { - // might not be the same anymore if disabled - profile = GetProfileFromGuid(profile.Guid, false, profile.IsSubProfile); - - // we've already announced this profile - if (currentProfile is not null) - if (currentProfile.Guid == profile.Guid) - announce = false; - - // update current profile before invoking event - currentProfile = profile; - - // raise event - Applied?.Invoke(profile, source); - - // send toast - // todo: localize me - if (announce) - { - LogManager.LogInformation("Profile {0} applied", profile.Name); - ToastManager.SendToast($"Profile {profile.Name} applied"); - } - } - - private static void PowerProfileManager_Deleted(PowerProfile powerProfile) - { - Profile profileToApply = null; - - // update main profiles - foreach (Profile profile in profiles.Values) - { - bool isCurrent = profile.PowerProfile == powerProfile.Guid; - if (isCurrent) - { - // sanitize profile - SanitizeProfile(profile); - - // update profile - UpdateOrCreateProfile(profile); - - if (currentProfile.Path.Equals(profile.Path, StringComparison.InvariantCultureIgnoreCase)) - profileToApply = profile; - } - } - - // update sub profiles - foreach (Profile profile in subProfiles) - { - bool isCurrent = profile.PowerProfile == powerProfile.Guid; - if (isCurrent) - { - // sanitize profile - SanitizeProfile(profile); - - // update profile - UpdateOrCreateProfile(profile); - - if (currentProfile.Guid == profile.Guid) - { - profileToApply = profile; - } - } - } - - if (profileToApply != null) - ApplyProfile(profileToApply); - - } - - private static void ProcessManager_ProcessStopped(ProcessEx processEx) - { - try - { - var profile = GetProfileFromPath(processEx.Path, true); - - // do not discard default profile - if (profile is null || profile.Default) - return; - - // raise event - Discarded?.Invoke(profile); - - if (profile.ErrorCode.HasFlag(ProfileErrorCode.Running)) - { - // update profile - UpdateOrCreateProfile(profile); - - // restore default profile - ApplyProfile(GetDefault()); - } - } - catch - { - } - } - - private static void ProcessManager_ProcessStarted(ProcessEx processEx, bool OnStartup) - { - try - { - var profile = GetProfileFromPath(processEx.Path, true); - - if (profile is null || profile.Default) - return; - - // update profile executable path - profile.Path = processEx.Path; - - // update profile - UpdateOrCreateProfile(profile); - } - catch - { - } - } - - private static void ProcessManager_ForegroundChanged(ProcessEx proc, ProcessEx back) - { - try - { - var profile = GetProfileFromPath(proc.Path, false); - - // update profile executable path - if (!profile.Default) - { - profile.Path = proc.Path; - UpdateOrCreateProfile(profile); - } - - // raise event - if (back is not null) - { - var backProfile = GetProfileFromPath(back.Path, false); - - if (backProfile != profile) - Discarded?.Invoke(backProfile); - } - - ApplyProfile(profile); - } - catch - { - } - } - - private static void ProfileCreated(object sender, FileSystemEventArgs e) - { - if (pendingCreation.Contains(e.FullPath)) - { - pendingCreation.Remove(e.FullPath); - return; - } - - ProcessProfile(e.FullPath, true); - } - - private static void ProfileDeleted(object sender, FileSystemEventArgs e) - { - if (pendingDeletion.Contains(e.FullPath)) - { - pendingDeletion.Remove(e.FullPath); - return; - } - - // not ideal - string ProfileName = e.Name.Replace(".json", ""); - Profile? profile = profiles.Values.FirstOrDefault(p => p.Name.Equals(ProfileName, StringComparison.InvariantCultureIgnoreCase)); - - // couldn't find a matching profile - if (profile is null) - return; - - // you can't delete default profile ! - if (profile.Default) - { - SerializeProfile(profile); - return; - } - - DeleteProfile(profile); - } - - private static bool HasDefault() - { - return profiles.Values.Count(a => a.Default) != 0; - } - - public static Profile GetDefault() - { - if (HasDefault()) - return profiles.Values.FirstOrDefault(a => a.Default); - return new Profile(); - } - - public static Profile GetCurrent() - { - if (currentProfile is not null) - return currentProfile; - - return GetDefault(); - } - - private static void ProcessProfile(string fileName, bool imported = false) - { - Profile profile = null; - try - { - var outputraw = File.ReadAllText(fileName); - var jObject = JObject.Parse(outputraw); - - // latest pre-versionning release - Version version = new("0.15.0.4"); - if (jObject.TryGetValue("Version", out var value)) - version = new Version(value.ToString()); - - switch (version.ToString()) - { - case "0.15.0.4": - { - outputraw = CommonUtils.RegexReplace(outputraw, "Generic.Dictionary(.*)System.Private.CoreLib\"", - "Generic.SortedDictionary$1System.Collections\""); - jObject = JObject.Parse(outputraw); - jObject.Remove("MotionSensivityArray"); - outputraw = jObject.ToString(); - } - break; - case "0.16.0.5": - { - outputraw = outputraw.Replace( - "\"System.Collections.Generic.SortedDictionary`2[[HandheldCompanion.Inputs.ButtonFlags, HandheldCompanion],[System.Boolean, System.Private.CoreLib]], System.Collections\"", - "\"System.Collections.Concurrent.ConcurrentDictionary`2[[HandheldCompanion.Inputs.ButtonFlags, HandheldCompanion],[System.Boolean, System.Private.CoreLib]], System.Collections.Concurrent\""); - } - break; - } - - profile = JsonConvert.DeserializeObject(outputraw, new JsonSerializerSettings - { - TypeNameHandling = TypeNameHandling.All - }); - } - catch (Exception ex) - { - LogManager.LogError("Could not parse profile {0}. {1}", fileName, ex.Message); - } - - // failed to parse - if (profile is null || profile.Name is null || profile.Path is null) - { - LogManager.LogError("Failed to parse profile {0}", fileName); - return; - } - - if (imported) - { - bool skipImported = true; - ManualResetEventSlim waitHandle = new ManualResetEventSlim(false); - - // UI thread - Application.Current.Dispatcher.Invoke(async () => - { - // todo: localize me - Task dialogTask = new Dialog(MainWindow.GetCurrent()) - { - Title = $"Importing profile for {profile.Name}", - Content = $"Would you like to import this profile to your database ?", - PrimaryButtonText = Resources.ProfilesPage_OK, - CloseButtonText = Resources.ProfilesPage_Cancel - }.ShowAsync(); - - ContentDialogResult result = await dialogTask; // await the task - - switch (result) - { - case ContentDialogResult.Primary: - skipImported = false; - break; - default: - skipImported = true; - break; - } - - // Signal the waiting thread that the dialog has been closed - waitHandle.Set(); - }); - - // Wait until the dialog has been closed - waitHandle.Wait(); - - // delete file and exit if user decided to skip this profile - if (skipImported) - { - File.Delete(fileName); - return; - } - } - - // if a profile for this path exists, make this one a subprofile - bool alreadyExist = Contains(profile.Path); - if (alreadyExist) - { - // give the profile a new guid - profile.Guid = Guid.NewGuid(); - - // set as sub-profile - profile.IsSubProfile = true; - profile.IsFavoriteSubProfile = false; - - // delete current file, profile manager will take care of creating a proper subprofile - File.Delete(fileName); - } - else - { - // if imported profile targeted file doesn't exist, use executable as path - bool pathExist = File.Exists(profile.Path); - if (!pathExist) - profile.Path = profile.Executable; - } - - UpdateOrCreateProfile(profile, UpdateSource.Serializer); - - // default specific - if (profile.Default) - ApplyProfile(profile, UpdateSource.Serializer); - } - - private static List pendingCreation = new(); - private static List pendingDeletion = new(); - - public static void DeleteProfile(Profile profile) - { - string profilePath = Path.Combine(ProfilesPath, profile.GetFileName()); - pendingDeletion.Add(profilePath); - - if (profiles.ContainsKey(profile.Path)) - { - // delete associated subprofiles - foreach (Profile subprofile in GetSubProfilesFromPath(profile.Path, false)) - DeleteSubProfile(subprofile); - - LogManager.LogInformation("Deleted subprofiles for profile {0}", profile); - - // Unregister application from HidHide - HidHide.UnregisterApplication(profile.Path); - - // Remove XInputPlus (extended compatibility) - XInputPlus.UnregisterApplication(profile); - - profiles.Remove(profile.Path); - - // warn owner +using HandheldCompanion.Controllers; +using HandheldCompanion.Controls; +using HandheldCompanion.Devices; +using HandheldCompanion.Misc; +using HandheldCompanion.Properties; +using HandheldCompanion.Utils; +using HandheldCompanion.Views; +using iNKORE.UI.WPF.Modern.Controls; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using static HandheldCompanion.Utils.XInputPlusUtils; + +namespace HandheldCompanion.Managers; + +public static class ProfileManager +{ + public const string DefaultName = "Default"; + + public static ConcurrentDictionary profiles = new(StringComparer.InvariantCultureIgnoreCase); + public static List subProfiles = new(); + + private static Profile currentProfile; + + private static string ProfilesPath; + + public static bool IsInitialized; + + static ProfileManager() + { + // initialiaze path(s) + ProfilesPath = Path.Combine(MainWindow.SettingsPath, "profiles"); + if (!Directory.Exists(ProfilesPath)) + Directory.CreateDirectory(ProfilesPath); + + ProcessManager.ForegroundChanged += ProcessManager_ForegroundChanged; + ProcessManager.ProcessStarted += ProcessManager_ProcessStarted; + ProcessManager.ProcessStopped += ProcessManager_ProcessStopped; + + PowerProfileManager.Deleted += PowerProfileManager_Deleted; + + ControllerManager.ControllerPlugged += ControllerManager_ControllerPlugged; + } + + public static FileSystemWatcher profileWatcher { get; set; } + + public static void Start() + { + // monitor profile files + profileWatcher = new FileSystemWatcher + { + Path = ProfilesPath, + EnableRaisingEvents = true, + IncludeSubdirectories = true, + Filter = "*.json", + NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size + }; + profileWatcher.Created += ProfileCreated; + profileWatcher.Deleted += ProfileDeleted; + + // process existing profiles + string[] fileEntries = Directory.GetFiles(ProfilesPath, "*.json", SearchOption.AllDirectories); + foreach (string fileName in fileEntries) + ProcessProfile(fileName, false); + + // check for default profile + if (!HasDefault()) + { + Layout deviceLayout = IDevice.GetCurrent().DefaultLayout.Clone() as Layout; + Profile defaultProfile = new() + { + Name = DefaultName, + Default = true, + Enabled = false, + Layout = deviceLayout, + LayoutTitle = LayoutTemplate.DefaultLayout.Name, + LayoutEnabled = true + }; + + UpdateOrCreateProfile(defaultProfile, UpdateSource.Creation); + } + + IsInitialized = true; + Initialized?.Invoke(); + + LogManager.LogInformation("{0} has started", "ProfileManager"); + } + + public static void Stop() + { + if (!IsInitialized) + return; + + IsInitialized = false; + + profileWatcher.Deleted -= ProfileDeleted; + profileWatcher.Dispose(); + + LogManager.LogInformation("{0} has stopped", "ProfileManager"); + } + + public static bool Contains(Profile profile) + { + foreach (var pr in profiles.Values) + if (pr.Path.Equals(profile.Path, StringComparison.InvariantCultureIgnoreCase)) + return true; + + return false; + } + + public static bool Contains(string fileName) + { + foreach (var pr in profiles.Values) + if (pr.Path.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)) + return true; + + return false; + } + + public static Profile GetProfileFromPath(string path, bool ignoreStatus) + { + // check if favorite sub profile exists for path + Profile profile = subProfiles.FirstOrDefault(pr => pr.Path == path && pr.IsFavoriteSubProfile); + + // get main profile from path instead + if (profile is null) + profile = profiles.Values.FirstOrDefault(a => a.Path.Equals(path, StringComparison.InvariantCultureIgnoreCase)); + + if (profile is null) + { + // otherwise, get profile from executable + string fileName = Path.GetFileName(path); + profile = profiles.Values.FirstOrDefault(a => a.Executable.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)); + + if (profile is null) + return GetDefault(); + } + + // ignore profile status (enabled/disabled) + if (ignoreStatus) + return profile; + + return profile.Enabled ? profile : GetDefault(); + } + + public static Profile GetProfileFromGuid(Guid Guid, bool ignoreStatus, bool isSubProfile = false) + { + Profile profile = null; + + if (isSubProfile) + profile = subProfiles.FirstOrDefault(pr => pr.Guid == Guid); + else + profile = profiles.Values.FirstOrDefault(pr => pr.Guid == Guid); + + // get profile from path + if (profile is null) + return GetDefault(); + + // ignore profile status (enabled/disabled) + if (ignoreStatus) + return profile; + + return profile.Enabled ? profile : GetDefault(); + } + + public static Profile[] GetSubProfilesFromPath(string path, bool ignoreStatus) + { + // get subprofile corresponding to path + List filteredSubProfiles = subProfiles.Where(pr => pr.Path == path).ToList(); + return filteredSubProfiles.OrderBy(pr => pr.Name).ToArray(); + } + + public static Profile GetProfileForSubProfile(Profile subProfile) + { + // if passed in profile is main profile + if (!subProfile.IsSubProfile || !profiles.ContainsKey(subProfile.Path)) + return subProfile; + + // get the main profile if it exists/loaded .. else return the profile itself + return profiles[subProfile.Path]; + } + + public static void SetSubProfileAsFavorite(Profile subProfile) + { + // remove favorite from all subprofiles + foreach (var profile in GetSubProfilesFromPath(subProfile.Path, false)) + { + profile.IsFavoriteSubProfile = false; + SerializeProfile(profile); + } + + // check if subProfile is not the main profile itself + if (subProfile.IsSubProfile) + { + subProfile.IsFavoriteSubProfile = true; + SerializeProfile(subProfile); + } + } + + public static void CycleSubProfiles(bool previous = false) + { + if (currentProfile == null) + return; + // called using previousSubProfile/nextSubProfile hotkeys + List subProfilesList = new(); + subProfilesList.Add(GetProfileForSubProfile(currentProfile)); // adds main profile as sub profile + subProfilesList.AddRange(GetSubProfilesFromPath(currentProfile.Path, false).ToList()); // adds all sub profiles + + // if profile does not have sub profiles -> do nothing + if (subProfilesList.Count <= 1) + return; + + // get index of currently applied profile + int currentIndex = subProfilesList.IndexOf(currentProfile); + int newIndex = currentIndex; + + // previous? decrement, next? increment + if (previous) + newIndex -= 1; + else + newIndex += 1; + + // ensure index is within list bounds, wrap if needed + newIndex = (newIndex + subProfilesList.Count) % subProfilesList.Count; + + // if for whatever reason index is out of bound -> return + if (newIndex < 0 || newIndex >= subProfilesList.Count) + return; + + // apply profile + Profile profileToApply = subProfilesList[newIndex]; + UpdateOrCreateProfile(profileToApply); + } + + + private static void ApplyProfile(Profile profile, UpdateSource source = UpdateSource.Background, + bool announce = true) + { + // might not be the same anymore if disabled + profile = GetProfileFromGuid(profile.Guid, false, profile.IsSubProfile); + + // we've already announced this profile + if (currentProfile is not null) + if (currentProfile.Guid == profile.Guid) + announce = false; + + // update current profile before invoking event + currentProfile = profile; + + // raise event + Applied?.Invoke(profile, source); + + // send toast + // todo: localize me + if (announce) + { + LogManager.LogInformation("Profile {0} applied", profile.Name); + ToastManager.SendToast($"Profile {profile.Name} applied"); + } + } + + private static void PowerProfileManager_Deleted(PowerProfile powerProfile) + { + Profile profileToApply = null; + + // update main profiles + foreach (Profile profile in profiles.Values) + { + bool isCurrent = profile.PowerProfile == powerProfile.Guid; + if (isCurrent) + { + // sanitize profile + SanitizeProfile(profile); + + // update profile + UpdateOrCreateProfile(profile); + + if (currentProfile.Path.Equals(profile.Path, StringComparison.InvariantCultureIgnoreCase)) + profileToApply = profile; + } + } + + // update sub profiles + foreach (Profile profile in subProfiles) + { + bool isCurrent = profile.PowerProfile == powerProfile.Guid; + if (isCurrent) + { + // sanitize profile + SanitizeProfile(profile); + + // update profile + UpdateOrCreateProfile(profile); + + if (currentProfile.Guid == profile.Guid) + { + profileToApply = profile; + } + } + } + + if (profileToApply != null) + ApplyProfile(profileToApply); + + } + + private static void ProcessManager_ProcessStopped(ProcessEx processEx) + { + try + { + var profile = GetProfileFromPath(processEx.Path, true); + + // do not discard default profile + if (profile is null || profile.Default) + return; + + // raise event + Discarded?.Invoke(profile); + + if (profile.ErrorCode.HasFlag(ProfileErrorCode.Running)) + { + // update profile + UpdateOrCreateProfile(profile); + + // restore default profile + ApplyProfile(GetDefault()); + } + } + catch + { + } + } + + private static void ProcessManager_ProcessStarted(ProcessEx processEx, bool OnStartup) + { + try + { + var profile = GetProfileFromPath(processEx.Path, true); + + if (profile is null || profile.Default) + return; + + // update profile executable path + profile.Path = processEx.Path; + + // update profile + UpdateOrCreateProfile(profile); + } + catch + { + } + } + + private static void ProcessManager_ForegroundChanged(ProcessEx proc, ProcessEx back) + { + try + { + var profile = GetProfileFromPath(proc.Path, false); + + // update profile executable path + if (!profile.Default) + { + profile.Path = proc.Path; + UpdateOrCreateProfile(profile); + } + + // raise event + if (back is not null) + { + var backProfile = GetProfileFromPath(back.Path, false); + + if (backProfile != profile) + Discarded?.Invoke(backProfile); + } + + ApplyProfile(profile); + } + catch + { + } + } + + private static void ProfileCreated(object sender, FileSystemEventArgs e) + { + if (pendingCreation.Contains(e.FullPath)) + { + pendingCreation.Remove(e.FullPath); + return; + } + + ProcessProfile(e.FullPath, true); + } + + private static void ProfileDeleted(object sender, FileSystemEventArgs e) + { + if (pendingDeletion.Contains(e.FullPath)) + { + pendingDeletion.Remove(e.FullPath); + return; + } + + // not ideal + string ProfileName = e.Name.Replace(".json", ""); + Profile? profile = profiles.Values.FirstOrDefault(p => p.Name.Equals(ProfileName, StringComparison.InvariantCultureIgnoreCase)); + + // couldn't find a matching profile + if (profile is null) + return; + + // you can't delete default profile ! + if (profile.Default) + { + SerializeProfile(profile); + return; + } + + DeleteProfile(profile); + } + + private static bool HasDefault() + { + return profiles.Values.Count(a => a.Default) != 0; + } + + public static Profile GetDefault() + { + if (HasDefault()) + return profiles.Values.FirstOrDefault(a => a.Default); + return new Profile(); + } + + public static Profile GetCurrent() + { + if (currentProfile is not null) + return currentProfile; + + return GetDefault(); + } + + private static void ProcessProfile(string fileName, bool imported = false) + { + Profile profile = null; + try + { + var outputraw = File.ReadAllText(fileName); + var jObject = JObject.Parse(outputraw); + + // latest pre-versionning release + Version version = new("0.15.0.4"); + if (jObject.TryGetValue("Version", out var value)) + version = new Version(value.ToString()); + + switch (version.ToString()) + { + case "0.15.0.4": + { + outputraw = CommonUtils.RegexReplace(outputraw, "Generic.Dictionary(.*)System.Private.CoreLib\"", + "Generic.SortedDictionary$1System.Collections\""); + jObject = JObject.Parse(outputraw); + jObject.Remove("MotionSensivityArray"); + outputraw = jObject.ToString(); + } + break; + case "0.16.0.5": + { + outputraw = outputraw.Replace( + "\"System.Collections.Generic.SortedDictionary`2[[HandheldCompanion.Inputs.ButtonFlags, HandheldCompanion],[System.Boolean, System.Private.CoreLib]], System.Collections\"", + "\"System.Collections.Concurrent.ConcurrentDictionary`2[[HandheldCompanion.Inputs.ButtonFlags, HandheldCompanion],[System.Boolean, System.Private.CoreLib]], System.Collections.Concurrent\""); + } + break; + } + + profile = JsonConvert.DeserializeObject(outputraw, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.All + }); + } + catch (Exception ex) + { + LogManager.LogError("Could not parse profile {0}. {1}", fileName, ex.Message); + } + + // failed to parse + if (profile is null || profile.Name is null || profile.Path is null) + { + LogManager.LogError("Failed to parse profile {0}", fileName); + return; + } + + if (imported) + { + bool skipImported = true; + ManualResetEventSlim waitHandle = new ManualResetEventSlim(false); + + // UI thread + Application.Current.Dispatcher.Invoke(async () => + { + // todo: localize me + Task dialogTask = new Dialog(MainWindow.GetCurrent()) + { + Title = $"Importing profile for {profile.Name}", + Content = $"Would you like to import this profile to your database ?", + PrimaryButtonText = Resources.ProfilesPage_OK, + CloseButtonText = Resources.ProfilesPage_Cancel + }.ShowAsync(); + + ContentDialogResult result = await dialogTask; // await the task + + switch (result) + { + case ContentDialogResult.Primary: + skipImported = false; + break; + default: + skipImported = true; + break; + } + + // Signal the waiting thread that the dialog has been closed + waitHandle.Set(); + }); + + // Wait until the dialog has been closed + waitHandle.Wait(); + + // delete file and exit if user decided to skip this profile + if (skipImported) + { + File.Delete(fileName); + return; + } + } + + // if a profile for this path exists, make this one a subprofile + bool alreadyExist = Contains(profile.Path); + if (alreadyExist) + { + // give the profile a new guid + profile.Guid = Guid.NewGuid(); + + // set as sub-profile + profile.IsSubProfile = true; + profile.IsFavoriteSubProfile = false; + + // delete current file, profile manager will take care of creating a proper subprofile + File.Delete(fileName); + } + else + { + // if imported profile targeted file doesn't exist, use executable as path + bool pathExist = File.Exists(profile.Path); + if (!pathExist) + profile.Path = profile.Executable; + } + + UpdateOrCreateProfile(profile, UpdateSource.Serializer); + + // default specific + if (profile.Default) + ApplyProfile(profile, UpdateSource.Serializer); + } + + private static List pendingCreation = new(); + private static List pendingDeletion = new(); + + public static void DeleteProfile(Profile profile) + { + string profilePath = Path.Combine(ProfilesPath, profile.GetFileName()); + pendingDeletion.Add(profilePath); + + if (profiles.ContainsKey(profile.Path)) + { + // delete associated subprofiles + foreach (Profile subprofile in GetSubProfilesFromPath(profile.Path, false)) + DeleteSubProfile(subprofile); + + LogManager.LogInformation("Deleted subprofiles for profile {0}", profile); + + // Unregister application from HidHide + HidHide.UnregisterApplication(profile.Path); + + // Remove XInputPlus (extended compatibility) + XInputPlus.UnregisterApplication(profile); + + _ = profiles.TryRemove(profile.Path, out Profile removedValue); + + // warn owner + bool isCurrent = false; + + if (currentProfile != null) + isCurrent = profile.Path.Equals(currentProfile.Path, StringComparison.InvariantCultureIgnoreCase); + + // raise event + Discarded?.Invoke(profile); + + // raise event(s) + Deleted?.Invoke(profile); + + // send toast + // todo: localize me + ToastManager.SendToast($"Profile {profile.Name} deleted"); + + LogManager.LogInformation("Deleted profile {0}", profilePath); + + // restore default profile + if (isCurrent) + ApplyProfile(GetDefault()); + } + + FileUtils.FileDelete(profilePath); + } + + public static void DeleteSubProfile(Profile subProfile) + { + string profilePath = Path.Combine(ProfilesPath, subProfile.GetFileName()); + pendingDeletion.Add(profilePath); + + if (subProfiles.Contains(subProfile)) + { + // remove sub profile from memory + subProfiles.Remove(subProfile); + + // warn owner bool isCurrent = false; if (currentProfile != null) - isCurrent = profile.Path.Equals(currentProfile.Path, StringComparison.InvariantCultureIgnoreCase); - - // raise event - Discarded?.Invoke(profile); - - // raise event(s) - Deleted?.Invoke(profile); - - // send toast - // todo: localize me - ToastManager.SendToast($"Profile {profile.Name} deleted"); - - LogManager.LogInformation("Deleted profile {0}", profilePath); - - // restore default profile - if (isCurrent) - ApplyProfile(GetDefault()); - } - - FileUtils.FileDelete(profilePath); - } - - public static void DeleteSubProfile(Profile subProfile) - { - string profilePath = Path.Combine(ProfilesPath, subProfile.GetFileName()); - pendingDeletion.Add(profilePath); - - if (subProfiles.Contains(subProfile)) - { - // remove sub profile from memory - subProfiles.Remove(subProfile); - - // warn owner - bool isCurrent = subProfile.Guid == currentProfile.Guid; - - // raise event - Discarded?.Invoke(subProfile); - - // raise event(s) - Deleted?.Invoke(subProfile); - - // send toast - // todo: localize me - ToastManager.SendToast($"Subprofile {subProfile.Name} deleted"); - - LogManager.LogInformation("Deleted subprofile {0}", profilePath); - - // restore main profile as favorite - if (isCurrent) - { - // apply the main profile if it still exists - Profile originalProfile = profiles.Values.FirstOrDefault(p => p.Path == subProfile.Path, GetDefault()); - ApplyProfile(originalProfile); - } - } - - FileUtils.FileDelete(profilePath); - } - - public static void SerializeProfile(Profile profile) - { - // prepare for writing - string profilePath = Path.Combine(ProfilesPath, profile.GetFileName()); - pendingCreation.Add(profilePath); - - // update profile version to current build - profile.Version = new Version(MainWindow.fileVersionInfo.FileVersion); - - string jsonString = JsonConvert.SerializeObject(profile, Formatting.Indented, new JsonSerializerSettings - { - TypeNameHandling = TypeNameHandling.All - }); - - try - { - if (FileUtils.IsFileWritable(profilePath)) - File.WriteAllText(profilePath, jsonString); - } - catch { } - } - - private static void SanitizeProfile(Profile profile) - { - profile.ErrorCode = ProfileErrorCode.None; - - if (profile.Default) - { - profile.ErrorCode |= ProfileErrorCode.Default; - } - else - { - var processpath = Path.GetDirectoryName(profile.Path); - - if (!Directory.Exists(processpath)) - profile.ErrorCode |= ProfileErrorCode.MissingPath; - - if (!File.Exists(profile.Path)) - profile.ErrorCode |= ProfileErrorCode.MissingExecutable; - - if (!FileUtils.IsDirectoryWritable(processpath)) - profile.ErrorCode |= ProfileErrorCode.MissingPermission; - - if (ProcessManager.GetProcesses(profile.Executable).Capacity > 0) - profile.ErrorCode |= ProfileErrorCode.Running; - } - - // looks like profile power profile was deleted, restore balanced - if (!PowerProfileManager.Contains(profile.PowerProfile)) - profile.PowerProfile = OSPowerMode.BetterPerformance; - } - - public static void UpdateOrCreateProfile(Profile profile, UpdateSource source = UpdateSource.Background) - { - LogManager.LogInformation($"Attempting to update/create profile {profile.Name} => sub profile? {profile.IsSubProfile}"); - bool isCurrent = false; - switch (source) - { - // if profile is created from QT -> apply it - case UpdateSource.QuickProfilesCreation: - isCurrent = true; - break; - case UpdateSource.ProfilesPageUpdateOnly: // when renaming main profile in ProfilesPage - isCurrent = false; - break; - default: - // check if this is current profile - isCurrent = currentProfile is null ? false : profile.Path.Equals(currentProfile.Path, StringComparison.InvariantCultureIgnoreCase); - break; - } - - // refresh error code - SanitizeProfile(profile); - - // used to get and store a few previous values - XInputPlusMethod prevWrapper = XInputPlusMethod.Disabled; - if (!profile.IsSubProfile && profiles.TryGetValue(profile.Path, out Profile prevProfile)) - { - prevWrapper = prevProfile.XInputPlus; - } - else if (profile.IsSubProfile) // TODO check if necessary - { - Profile prevSubProfile = subProfiles.FirstOrDefault(sub => sub.Guid == profile.Guid); - if (prevSubProfile != null) - prevWrapper = prevSubProfile.XInputPlus; - } - - // update database - if (profile.IsSubProfile) - { - // remove sub profile if it already exists, then add the updated one - subProfiles = subProfiles.Where(pr => pr.Guid != profile.Guid).ToList(); - subProfiles.Add(profile); - } - else - profiles[profile.Path] = profile; - - // raise event(s) - Updated?.Invoke(profile, source, isCurrent); - - if (source == UpdateSource.Serializer) - return; - - // do not update wrapper and cloaking from default profile - if (!profile.Default) - { - // update wrapper - if (!UpdateProfileWrapper(profile)) - { - // restore previous XInputPlus mode if failed to update - profile.XInputPlus = prevWrapper; - source = UpdateSource.Background; - } - - // update cloaking - UpdateProfileCloaking(profile); - } - - // apply profile (silently) - LogManager.LogInformation($"Checking if profile: {profile} is current => {isCurrent}"); - if (isCurrent) - { - SetSubProfileAsFavorite(profile); // if sub profile, set it as favorite for main profile - ApplyProfile(profile, source); - } - - // serialize profile - SerializeProfile(profile); - } - - public static bool UpdateProfileCloaking(Profile profile) - { - switch (profile.ErrorCode) - { - case ProfileErrorCode.MissingExecutable: - case ProfileErrorCode.MissingPath: - case ProfileErrorCode.Default: - return false; - } - - switch (profile.Whitelisted) - { - case true: - return HidHide.RegisterApplication(profile.Path); - default: - case false: - return HidHide.UnregisterApplication(profile.Path); - } - } - - public static bool UpdateProfileWrapper(Profile profile) - { - switch (profile.ErrorCode) - { - case ProfileErrorCode.MissingPermission: - case ProfileErrorCode.MissingPath: - case ProfileErrorCode.Running: - case ProfileErrorCode.Default: - return false; - } - - switch (profile.XInputPlus) - { - case XInputPlusMethod.Redirection: - return XInputPlus.RegisterApplication(profile); - default: - case XInputPlusMethod.Disabled: - case XInputPlusMethod.Injection: - return XInputPlus.UnregisterApplication(profile); - } - } - - private static void ControllerManager_ControllerPlugged(IController Controller, bool IsPowerCycling) - { - // we're only interest in virtual, XInput controllers - if (Controller is not XInputController || !Controller.IsVirtual()) - return; - - foreach (var profile in profiles.Values) - UpdateProfileWrapper(profile); - } - - public static Profile? GetProfileWithDefaultLayout() => profiles.Values.FirstOrDefault(p => p.Layout.IsDefaultLayout); - - #region events - - public static event DeletedEventHandler Deleted; - - public delegate void DeletedEventHandler(Profile profile); - - public static event UpdatedEventHandler Updated; - - public delegate void UpdatedEventHandler(Profile profile, UpdateSource source, bool isCurrent); - - public static event AppliedEventHandler Applied; - - public delegate void AppliedEventHandler(Profile profile, UpdateSource source); - - public static event DiscardedEventHandler Discarded; - - public delegate void DiscardedEventHandler(Profile profile); - - public static event InitializedEventHandler Initialized; - - public delegate void InitializedEventHandler(); - - #endregion + isCurrent = subProfile.Guid == currentProfile.Guid; + + // raise event + Discarded?.Invoke(subProfile); + + // raise event(s) + Deleted?.Invoke(subProfile); + + // send toast + // todo: localize me + ToastManager.SendToast($"Subprofile {subProfile.Name} deleted"); + + LogManager.LogInformation("Deleted subprofile {0}", profilePath); + + // restore main profile as favorite + if (isCurrent) + { + // apply the main profile if it still exists + Profile originalProfile = profiles.Values.FirstOrDefault(p => p.Path == subProfile.Path, GetDefault()); + ApplyProfile(originalProfile); + } + } + + FileUtils.FileDelete(profilePath); + } + + public static void SerializeProfile(Profile profile) + { + // prepare for writing + string profilePath = Path.Combine(ProfilesPath, profile.GetFileName()); + pendingCreation.Add(profilePath); + + // update profile version to current build + profile.Version = new Version(MainWindow.fileVersionInfo.FileVersion); + + string jsonString = JsonConvert.SerializeObject(profile, Formatting.Indented, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.All + }); + + try + { + if (FileUtils.IsFileWritable(profilePath)) + File.WriteAllText(profilePath, jsonString); + } + catch { } + } + + private static void SanitizeProfile(Profile profile) + { + profile.ErrorCode = ProfileErrorCode.None; + + if (profile.Default) + { + profile.ErrorCode |= ProfileErrorCode.Default; + } + else + { + var processpath = Path.GetDirectoryName(profile.Path); + + if (!Directory.Exists(processpath)) + profile.ErrorCode |= ProfileErrorCode.MissingPath; + + if (!File.Exists(profile.Path)) + profile.ErrorCode |= ProfileErrorCode.MissingExecutable; + + if (!FileUtils.IsDirectoryWritable(processpath)) + profile.ErrorCode |= ProfileErrorCode.MissingPermission; + + if (ProcessManager.GetProcesses(profile.Executable).Capacity > 0) + profile.ErrorCode |= ProfileErrorCode.Running; + } + + // looks like profile power profile was deleted, restore balanced + if (!PowerProfileManager.Contains(profile.PowerProfile)) + profile.PowerProfile = OSPowerMode.BetterPerformance; + } + + public static void UpdateOrCreateProfile(Profile profile, UpdateSource source = UpdateSource.Background) + { + LogManager.LogInformation($"Attempting to update/create profile {profile.Name} => sub profile? {profile.IsSubProfile}"); + bool isCurrent = false; + switch (source) + { + // if profile is created from QT -> apply it + case UpdateSource.QuickProfilesCreation: + isCurrent = true; + break; + case UpdateSource.ProfilesPageUpdateOnly: // when renaming main profile in ProfilesPage + isCurrent = false; + break; + default: + // check if this is current profile + isCurrent = currentProfile is null ? false : profile.Path.Equals(currentProfile.Path, StringComparison.InvariantCultureIgnoreCase); + break; + } + + // refresh error code + SanitizeProfile(profile); + + // used to get and store a few previous values + XInputPlusMethod prevWrapper = XInputPlusMethod.Disabled; + if (!profile.IsSubProfile && profiles.TryGetValue(profile.Path, out Profile prevProfile)) + { + prevWrapper = prevProfile.XInputPlus; + } + else if (profile.IsSubProfile) // TODO check if necessary + { + Profile prevSubProfile = subProfiles.FirstOrDefault(sub => sub.Guid == profile.Guid); + if (prevSubProfile != null) + prevWrapper = prevSubProfile.XInputPlus; + } + + // update database + if (profile.IsSubProfile) + { + // remove sub profile if it already exists, then add the updated one + subProfiles = subProfiles.Where(pr => pr.Guid != profile.Guid).ToList(); + subProfiles.Add(profile); + } + else + profiles[profile.Path] = profile; + + // raise event(s) + Updated?.Invoke(profile, source, isCurrent); + + if (source == UpdateSource.Serializer) + return; + + // do not update wrapper and cloaking from default profile + if (!profile.Default) + { + // update wrapper + if (!UpdateProfileWrapper(profile)) + { + // restore previous XInputPlus mode if failed to update + profile.XInputPlus = prevWrapper; + source = UpdateSource.Background; + } + + // update cloaking + UpdateProfileCloaking(profile); + } + + // apply profile (silently) + LogManager.LogInformation($"Checking if profile: {profile} is current => {isCurrent}"); + if (isCurrent) + { + SetSubProfileAsFavorite(profile); // if sub profile, set it as favorite for main profile + ApplyProfile(profile, source); + } + + // serialize profile + SerializeProfile(profile); + } + + public static bool UpdateProfileCloaking(Profile profile) + { + switch (profile.ErrorCode) + { + case ProfileErrorCode.MissingExecutable: + case ProfileErrorCode.MissingPath: + case ProfileErrorCode.Default: + return false; + } + + switch (profile.Whitelisted) + { + case true: + return HidHide.RegisterApplication(profile.Path); + default: + case false: + return HidHide.UnregisterApplication(profile.Path); + } + } + + public static bool UpdateProfileWrapper(Profile profile) + { + switch (profile.ErrorCode) + { + case ProfileErrorCode.MissingPermission: + case ProfileErrorCode.MissingPath: + case ProfileErrorCode.Running: + case ProfileErrorCode.Default: + return false; + } + + switch (profile.XInputPlus) + { + case XInputPlusMethod.Redirection: + return XInputPlus.RegisterApplication(profile); + default: + case XInputPlusMethod.Disabled: + case XInputPlusMethod.Injection: + return XInputPlus.UnregisterApplication(profile); + } + } + + private static void ControllerManager_ControllerPlugged(IController Controller, bool IsPowerCycling) + { + // we're only interest in virtual, XInput controllers + if (Controller is not XInputController || !Controller.IsVirtual()) + return; + + foreach (var profile in profiles.Values) + UpdateProfileWrapper(profile); + } + + public static Profile? GetProfileWithDefaultLayout() => profiles.Values.FirstOrDefault(p => p.Layout.IsDefaultLayout); + + #region events + + public static event DeletedEventHandler Deleted; + + public delegate void DeletedEventHandler(Profile profile); + + public static event UpdatedEventHandler Updated; + + public delegate void UpdatedEventHandler(Profile profile, UpdateSource source, bool isCurrent); + + public static event AppliedEventHandler Applied; + + public delegate void AppliedEventHandler(Profile profile, UpdateSource source); + + public static event DiscardedEventHandler Discarded; + + public delegate void DiscardedEventHandler(Profile profile); + + public static event InitializedEventHandler Initialized; + + public delegate void InitializedEventHandler(); + + #endregion } \ No newline at end of file diff --git a/HandheldCompanion/Misc/MotherboardInfo.cs b/HandheldCompanion/Misc/MotherboardInfo.cs index 5f1666054..3829feba7 100644 --- a/HandheldCompanion/Misc/MotherboardInfo.cs +++ b/HandheldCompanion/Misc/MotherboardInfo.cs @@ -1,459 +1,370 @@ -using HandheldCompanion.Devices; -using System.Collections.Generic; -using System.Management; - -namespace HandheldCompanion; - -public static class MotherboardInfo -{ - private static readonly ManagementObjectSearcher baseboardSearcher = new("root\\CIMV2", "SELECT * FROM Win32_BaseBoard"); - private static ManagementObjectCollection baseboardCollection = baseboardSearcher.Get(); - - private static readonly ManagementObjectSearcher motherboardSearcher = new("root\\CIMV2", "SELECT * FROM Win32_MotherboardDevice"); - private static ManagementObjectCollection motherboardCollection = motherboardSearcher.Get(); - - private static readonly ManagementObjectSearcher processorSearcher = new("root\\CIMV2", "SELECT * FROM Win32_Processor"); - private static ManagementObjectCollection processorCollection = processorSearcher.Get(); - - private static readonly ManagementObjectSearcher displaySearcher = new("root\\CIMV2", "SELECT * FROM Win32_DisplayConfiguration"); - private static ManagementObjectCollection displayCollection = displaySearcher.Get(); - - private static readonly ManagementObjectSearcher videoControllerSearcher = new("root\\CIMV2", "SELECT * FROM Win32_VideoController"); - private static ManagementObjectCollection videoControllerCollection = videoControllerSearcher.Get(); - - public static string Availability - { - get - { - foreach (ManagementObject queryObj in motherboardCollection) - { - var query = queryObj["Availability"]; - if (query is not null) - if (int.TryParse(query.ToString(), out var value)) - return GetAvailability(value); - } - - return string.Empty; - } - } - - public static List DisplayDescription - { - get - { - List strings = new List(); - foreach (ManagementObject queryObj in displayCollection) - { - var query = queryObj["Description"]; - if (query is not null) - strings.Add(query.ToString().ToUpper()); - } - - return strings; - } - } - - public static bool HostingBoard +using HandheldCompanion.Devices; +using HandheldCompanion.Views; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Management; +using System.Runtime.CompilerServices; + +namespace HandheldCompanion; + +public static class MotherboardInfo +{ + private static readonly ManagementObjectSearcher baseboardSearcher = new("root\\CIMV2", "SELECT * FROM Win32_BaseBoard"); + private static ManagementObjectCollection? baseboardCollection; + + private static readonly ManagementObjectSearcher motherboardSearcher = new("root\\CIMV2", "SELECT * FROM Win32_MotherboardDevice"); + private static ManagementObjectCollection? motherboardCollection; + + private static readonly ManagementObjectSearcher processorSearcher = new("root\\CIMV2", "SELECT * FROM Win32_Processor"); + private static ManagementObjectCollection? processorCollection; + + private static readonly ManagementObjectSearcher displaySearcher = new("root\\CIMV2", "SELECT * FROM Win32_DisplayConfiguration"); + private static ManagementObjectCollection? displayCollection; + + private static readonly ManagementObjectSearcher videoControllerSearcher = new("root\\CIMV2", "SELECT * FROM Win32_VideoController"); + private static ManagementObjectCollection? videoControllerCollection; + + private static object cacheLock = new(); + private static Dictionary cache = new(); + + private static readonly string cacheDirectory; + private const string fileName = "motherboard.json"; + + static MotherboardInfo() { - get - { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["HostingBoard"]; - if (query is not null) - if (query.ToString() == "True") - return true; - } - - return false; - } + cacheDirectory = Path.Combine(MainWindow.SettingsPath, "cache"); + if (!Directory.Exists(cacheDirectory)) + Directory.CreateDirectory(cacheDirectory); } - public static string InstallDate + public static void Collect() { - get + if (!loadCache()) { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["InstallDate"]; - if (query is not null) - return ConvertToDateTime(query.ToString()); - } - - return string.Empty; + baseboardCollection = baseboardSearcher.Get(); + motherboardCollection = motherboardSearcher.Get(); + processorCollection = processorSearcher.Get(); + displayCollection = displaySearcher.Get(); + videoControllerCollection = videoControllerSearcher.Get(); } - } - - private static string _Manufacturer; - public static string Manufacturer + } + + public static string Availability + { + get + { + string result = Convert.ToString(queryCacheValue(motherboardCollection, "Availability")); + if (int.TryParse(result, out var value)) + return GetAvailability(value); + else + return result; + } + } + + public static List DisplayDescription + { + get + { + return (List)queryCacheValue(displayCollection, "Description"); + } + } + + public static bool HostingBoard + { + get + { + return Convert.ToBoolean(queryCacheValue(baseboardCollection, "HostingBoard")); + } + } + + public static string InstallDate + { + get + { + string result = Convert.ToString(queryCacheValue(baseboardCollection, "InstallDate")); + if (!string.IsNullOrEmpty(result)) + return ConvertToDateTime(result); + else + return result; + } + } + + public static string Manufacturer + { + get + { + return Convert.ToString(queryCacheValue(baseboardCollection, "Manufacturer")); + } + } + + public static string Model + { + get + { + return Convert.ToString(queryCacheValue(baseboardCollection, "Model")); + } + } + + public static int NumberOfCores + { + get + { + return Convert.ToInt32(queryCacheValue(processorCollection, "NumberOfCores")); + } + } + + public static string PartNumber + { + get + { + return Convert.ToString(queryCacheValue(baseboardCollection, "PartNumber")); + } + } + + public static string PNPDeviceID + { + get + { + return Convert.ToString(queryCacheValue(motherboardCollection, "PNPDeviceID")); + } + } + + public static string PrimaryBusType + { + get + { + return Convert.ToString(queryCacheValue(motherboardCollection, "PrimaryBusType")); + } + } + + public static string ProcessorID + { + get + { + return Convert.ToString(queryCacheValue(processorCollection, "processorID")).TrimEnd(); + } + } + + public static string ProcessorName + { + get + { + return Convert.ToString(queryCacheValue(processorCollection, "Name")).TrimEnd(); + } + } + + public static string ProcessorManufacturer + { + get + { + return Convert.ToString(queryCacheValue(processorCollection, "Manufacturer")).TrimEnd(); + } + } + + public static uint ProcessorMaxClockSpeed + { + get + { + return Convert.ToUInt32(queryCacheValue(processorCollection, "MaxClockSpeed")); + } + } + + private static uint _ProcessorMaxTurboSpeed = 0; + public static uint ProcessorMaxTurboSpeed + { + get + { + if (_ProcessorMaxTurboSpeed != 0) + return _ProcessorMaxTurboSpeed; + + _ProcessorMaxTurboSpeed = IDevice.GetCurrent().CpuClock; + + return _ProcessorMaxTurboSpeed; + } + } + + public static string Product + { + get + { + return Convert.ToString(queryCacheValue(baseboardCollection, "Product")); + } + } + + public static bool Removable + { + get + { + return Convert.ToBoolean(queryCacheValue(baseboardCollection, "Removable")); + } + } + + public static bool Replaceable + { + get + { + return Convert.ToBoolean(queryCacheValue(baseboardCollection, "Replaceable")); + } + } + + public static string RevisionNumber + { + get + { + return Convert.ToString(queryCacheValue(motherboardCollection, "RevisionNumber")); + } + } + + public static string SecondaryBusType + { + get + { + return Convert.ToString(queryCacheValue(motherboardCollection, "SecondaryBusType")); + } + } + + public static string SerialNumber + { + get + { + return Convert.ToString(queryCacheValue(baseboardCollection, "SerialNumber")); + } + } + + public static string Status + { + get + { + return Convert.ToString(queryCacheValue(baseboardCollection, "Status")); + } + } + + public static string SystemName + { + get + { + return Convert.ToString(queryCacheValue(motherboardCollection, "SystemName")); + } + } + + public static string Version + { + get + { + return Convert.ToString(queryCacheValue(baseboardCollection, "Version")); + } + } + + private static object queryCacheValue(ManagementObjectCollection collection, string query, [CallerArgumentExpression("collection")] string collectionName = "") { - get + if (!cache.ContainsKey($"{collectionName}-{query}")) { - if (!string.IsNullOrEmpty(_Manufacturer)) - return _Manufacturer; - - foreach (ManagementObject queryObj in baseboardCollection) + if (collection is not null) { - var query = queryObj["Manufacturer"]; - if (query is not null) + foreach (ManagementObject queryObj in collection) { - _Manufacturer = query.ToString(); - break; + object queryResult = queryObj[query]; + if (queryResult is not null) + { + cache.Add($"{collectionName}-{query}", queryResult); + writeCache(); + break; + } } } - - return _Manufacturer; } - } - - public static string Model - { - get - { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["Model"]; - if (query is not null) - return query.ToString(); - } - return string.Empty; - } + if (cache.TryGetValue($"{collectionName}-{query}", out var result)) + return result; + + return string.Empty; + } + + private static string GetAvailability(int availability) + { + switch (availability) + { + case 1: return "Other"; + case 2: return "Unknown"; + case 3: return "Running or Full Power"; + case 4: return "Warning"; + case 5: return "In Test"; + case 6: return "Not Applicable"; + case 7: return "Power Off"; + case 8: return "Off Line"; + case 9: return "Off Duty"; + case 10: return "Degraded"; + case 11: return "Not Installed"; + case 12: return "Install Error"; + case 13: return "Power Save - Unknown"; + case 14: return "Power Save - Low Power Mode"; + case 15: return "Power Save - Standby"; + case 16: return "Power Cycle"; + case 17: return "Power Save - Warning"; + default: return "Unknown"; + } + } + + private static string ConvertToDateTime(string unconvertedTime) + { + var convertedTime = ""; + var year = int.Parse(unconvertedTime.Substring(0, 4)); + var month = int.Parse(unconvertedTime.Substring(4, 2)); + var date = int.Parse(unconvertedTime.Substring(6, 2)); + var hours = int.Parse(unconvertedTime.Substring(8, 2)); + var minutes = int.Parse(unconvertedTime.Substring(10, 2)); + var seconds = int.Parse(unconvertedTime.Substring(12, 2)); + var meridian = "AM"; + if (hours > 12) + { + hours -= 12; + meridian = "PM"; + } + + convertedTime = date + "/" + month + "/" + year + " " + + hours + ":" + minutes + ":" + seconds + " " + meridian; + return convertedTime; } - private static int _NumberOfCores = 0; - public static int NumberOfCores + private static bool loadCache() { - get + lock (cacheLock) { - if (_NumberOfCores != 0) - return _NumberOfCores; - - foreach (ManagementObject queryObj in processorCollection) + string cacheFile = Path.Combine(cacheDirectory, fileName); + if (File.Exists(cacheFile)) { - var query = queryObj["NumberOfCores"]; - if (query is not null) - { - if (int.TryParse(query.ToString(), out var value)) - _NumberOfCores = value; - break; - } - } + string cacheJSON = File.ReadAllText(cacheFile); - return _NumberOfCores; - } - } - - public static string PartNumber - { - get - { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["PartNumber"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - public static string PNPDeviceID - { - get - { - foreach (ManagementObject queryObj in motherboardCollection) - { - var query = queryObj["PNPDeviceID"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - public static string PrimaryBusType - { - get - { - foreach (ManagementObject queryObj in motherboardCollection) - { - var query = queryObj["PrimaryBusType"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - public static string ProcessorID - { - get - { - foreach (ManagementObject queryObj in processorCollection) - { - var query = queryObj["processorID"]; - - if (query is not null) - return query.ToString().TrimEnd(); - } - - return string.Empty; - } - } - - public static string ProcessorName - { - get - { - foreach (ManagementObject queryObj in processorCollection) - { - var query = queryObj["Name"]; - - if (query is not null) - return query.ToString().TrimEnd(); - } - - return string.Empty; - } - } - - public static string ProcessorManufacturer - { - get - { - foreach (ManagementObject queryObj in processorCollection) - { - var query = queryObj["Manufacturer"]; - - if (query is not null) - return query.ToString().TrimEnd(); - } - - return string.Empty; - } - } - - private static uint _ProcessorMaxClockSpeed = 0; - public static uint ProcessorMaxClockSpeed - { - get - { - if (_ProcessorMaxClockSpeed != 0) - return _ProcessorMaxClockSpeed; + Dictionary? cache = JsonConvert.DeserializeObject>(cacheJSON, new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.All + }); - foreach (ManagementObject queryObj in processorCollection) - { - var query = queryObj["MaxClockSpeed"]; - if (query is not null) + if (cache is not null) { - if (uint.TryParse(query.ToString(), out var value)) - _ProcessorMaxClockSpeed = value; - break; + MotherboardInfo.cache = cache; + return true; } } - - return _ProcessorMaxClockSpeed; - } - } - - private static uint _ProcessorMaxTurboSpeed = 0; - public static uint ProcessorMaxTurboSpeed - { - get - { - if (_ProcessorMaxTurboSpeed != 0) - return _ProcessorMaxTurboSpeed; - - _ProcessorMaxTurboSpeed = IDevice.GetCurrent().CpuClock; - - return _ProcessorMaxTurboSpeed; - } - } - - public static string Product - { - get - { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["Product"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - public static bool Removable - { - get - { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["Removable"]; - if (query is not null) - if (query.ToString() == "True") - return true; - } - + return false; } } - public static bool Replaceable - { - get - { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["Replaceable"]; - if (query is not null) - if (query.ToString() == "True") - return true; - } - - return false; - } - } - - public static string RevisionNumber - { - get - { - foreach (ManagementObject queryObj in motherboardCollection) - { - var query = queryObj["RevisionNumber"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - public static string SecondaryBusType - { - get - { - foreach (ManagementObject queryObj in motherboardCollection) - { - var query = queryObj["SecondaryBusType"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - public static string SerialNumber + private static void writeCache() { - get + lock (cacheLock) { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["SerialNumber"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } + string cacheFile = Path.Combine(cacheDirectory, fileName); - public static string Status - { - get - { - foreach (ManagementObject queryObj in baseboardCollection) + string jsonString = JsonConvert.SerializeObject(cache, Formatting.Indented, new JsonSerializerSettings { - var query = queryObj["Status"]; - if (query is not null) - return query.ToString(); - } + TypeNameHandling = TypeNameHandling.All + }); - return string.Empty; + File.WriteAllText(cacheFile, jsonString); } } - - public static string SystemName - { - get - { - foreach (ManagementObject queryObj in motherboardCollection) - { - var query = queryObj["SystemName"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - public static string Version - { - get - { - foreach (ManagementObject queryObj in baseboardCollection) - { - var query = queryObj["Version"]; - if (query is not null) - return query.ToString(); - } - - return string.Empty; - } - } - - private static string GetAvailability(int availability) - { - switch (availability) - { - case 1: return "Other"; - case 2: return "Unknown"; - case 3: return "Running or Full Power"; - case 4: return "Warning"; - case 5: return "In Test"; - case 6: return "Not Applicable"; - case 7: return "Power Off"; - case 8: return "Off Line"; - case 9: return "Off Duty"; - case 10: return "Degraded"; - case 11: return "Not Installed"; - case 12: return "Install Error"; - case 13: return "Power Save - Unknown"; - case 14: return "Power Save - Low Power Mode"; - case 15: return "Power Save - Standby"; - case 16: return "Power Cycle"; - case 17: return "Power Save - Warning"; - default: return "Unknown"; - } - } - - private static string ConvertToDateTime(string unconvertedTime) - { - var convertedTime = ""; - var year = int.Parse(unconvertedTime.Substring(0, 4)); - var month = int.Parse(unconvertedTime.Substring(4, 2)); - var date = int.Parse(unconvertedTime.Substring(6, 2)); - var hours = int.Parse(unconvertedTime.Substring(8, 2)); - var minutes = int.Parse(unconvertedTime.Substring(10, 2)); - var seconds = int.Parse(unconvertedTime.Substring(12, 2)); - var meridian = "AM"; - if (hours > 12) - { - hours -= 12; - meridian = "PM"; - } - - convertedTime = date + "/" + month + "/" + year + " " + - hours + ":" + minutes + ":" + seconds + " " + meridian; - return convertedTime; - } } \ No newline at end of file diff --git a/HandheldCompanion/Platforms/LibreHardwareMonitor.cs b/HandheldCompanion/Platforms/LibreHardwareMonitor.cs index 2635e9f8a..9ad670884 100644 --- a/HandheldCompanion/Platforms/LibreHardwareMonitor.cs +++ b/HandheldCompanion/Platforms/LibreHardwareMonitor.cs @@ -10,6 +10,7 @@ public class LibreHardwareMonitor : IPlatform private Timer updateTimer; private int updateInterval = 1000; + private object updateLock = new(); public float? CPULoad; public float? CPUClock; @@ -59,29 +60,37 @@ public override bool Stop(bool kill = false) if (updateTimer is not null) updateTimer.Stop(); - if (computer is not null) - computer.Close(); + // wait until all tasks are complete + lock (updateLock) + { + if (computer is not null) + computer.Close(); + } return base.Stop(kill); } private void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) { - // pull temperature sensor - foreach (var hardware in computer.Hardware) + lock (updateLock) { - hardware.Update(); - switch (hardware.HardwareType) + // pull temperature sensor + foreach (IHardware? hardware in computer.Hardware) { - case HardwareType.Cpu: - HandleCPU(hardware); - break; - case HardwareType.Memory: - HandleMemory(hardware); - break; - case HardwareType.Battery: - HandleBattery(hardware); - break; + hardware.Update(); + + switch (hardware.HardwareType) + { + case HardwareType.Cpu: + HandleCPU(hardware); + break; + case HardwareType.Memory: + HandleMemory(hardware); + break; + case HardwareType.Battery: + HandleBattery(hardware); + break; + } } } } diff --git a/HandheldCompanion/Processors/AMDProcessor.cs b/HandheldCompanion/Processors/AMDProcessor.cs index a55f3a2b6..52d637111 100644 --- a/HandheldCompanion/Processors/AMDProcessor.cs +++ b/HandheldCompanion/Processors/AMDProcessor.cs @@ -3,7 +3,6 @@ using HandheldCompanion.Processors.AMD; using System; using System.Threading; -using System.Timers; namespace HandheldCompanion.Processors; @@ -65,77 +64,6 @@ public AMDProcessor() IsInitialized = true; } - - foreach (var type in (PowerType[])Enum.GetValues(typeof(PowerType))) - { - // write default limits - m_Limits[type] = 0; - m_PrevLimits[type] = 0; - - /* - // write default values - m_Values[type] = 0; - m_PrevValues[type] = 0; - */ - } - } - - public override void Initialize() - { - updateTimer.Elapsed += UpdateTimer_Elapsed; - base.Initialize(); - } - - public override void Stop() - { - updateTimer.Elapsed -= UpdateTimer_Elapsed; - base.Stop(); - } - - protected override void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e) - { - if (Monitor.TryEnter(IsBusy)) - { - RyzenAdj.get_table_values(ry); - RyzenAdj.refresh_table(ry); - - // read limit(s) - var limit_fast = (int)RyzenAdj.get_fast_limit(ry); - var limit_slow = (int)RyzenAdj.get_slow_limit(ry); - var limit_stapm = (int)RyzenAdj.get_stapm_limit(ry); - - if (limit_fast != 0) - m_Limits[PowerType.Fast] = limit_fast; - if (limit_slow != 0) - m_Limits[PowerType.Slow] = limit_slow; - if (limit_stapm != 0) - m_Limits[PowerType.Stapm] = limit_stapm; - - // read value(s) - var value_fast = (int)RyzenAdj.get_fast_value(ry); - var value_slow = (int)RyzenAdj.get_slow_value(ry); - var value_stapm = (int)RyzenAdj.get_stapm_value(ry); - - while (value_fast == 0) - value_fast = (int)RyzenAdj.get_fast_value(ry); - while (value_slow == 0) - value_slow = (int)RyzenAdj.get_slow_value(ry); - while (value_stapm == 0) - value_stapm = (int)RyzenAdj.get_stapm_value(ry); - - m_Values[PowerType.Fast] = value_fast; - m_Values[PowerType.Slow] = value_slow; - m_Values[PowerType.Stapm] = value_stapm; - - // read gfx_clk - var gfx_clk = (int)RyzenAdj.get_gfx_clk(ry); - if (gfx_clk != 0) - m_Misc["gfx_clk"] = gfx_clk; - - base.UpdateTimer_Elapsed(sender, e); - - Monitor.Exit(IsBusy); - } } public override void SetTDPLimit(PowerType type, double limit, bool immediate, int result) @@ -143,7 +71,7 @@ public override void SetTDPLimit(PowerType type, double limit, bool immediate, i if (ry == IntPtr.Zero) return; - if (Monitor.TryEnter(IsBusy)) + lock (updateLock) { // 15W : 15000 limit *= 1000; @@ -164,14 +92,12 @@ public override void SetTDPLimit(PowerType type, double limit, bool immediate, i } base.SetTDPLimit(type, limit, immediate, error); - - Monitor.Exit(IsBusy); } } public override void SetGPUClock(double clock, int result) { - if (Monitor.TryEnter(IsBusy)) + lock (updateLock) { switch (family) { @@ -226,8 +152,6 @@ public override void SetGPUClock(double clock, int result) } break; } - - Monitor.Exit(IsBusy); } } diff --git a/HandheldCompanion/Processors/IntelProcessor.cs b/HandheldCompanion/Processors/IntelProcessor.cs index 30965da88..16a4dc684 100644 --- a/HandheldCompanion/Processors/IntelProcessor.cs +++ b/HandheldCompanion/Processors/IntelProcessor.cs @@ -1,7 +1,5 @@ using HandheldCompanion.Processors.Intel; -using System; using System.Threading; -using System.Timers; namespace HandheldCompanion.Processors; @@ -40,84 +38,12 @@ public IntelProcessor() CanChangeGPU = true; break; } - - foreach (var type in (PowerType[])Enum.GetValues(typeof(PowerType))) - { - // write default limits - m_Limits[type] = 0; - m_PrevLimits[type] = 0; - - /* - // write default values : not supported - m_Values[type] = -1; - m_PrevValues[type] = -1; - */ - } - } - } - - public override void Initialize() - { - updateTimer.Elapsed += UpdateTimer_Elapsed; - base.Initialize(); - } - - public override void Stop() - { - updateTimer.Elapsed -= UpdateTimer_Elapsed; - base.Stop(); - } - - protected override void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e) - { - if (Monitor.TryEnter(IsBusy)) - { - // read limit(s) - var limit_short = platform.get_short_limit(false); - var limit_long = platform.get_long_limit(false); - - if (limit_short != -1) - m_Limits[PowerType.Fast] = limit_short; - if (limit_long != -1) - m_Limits[PowerType.Slow] = limit_long; - - // read msr limit(s) - var msr_short = platform.get_short_limit(true); - var msr_long = platform.get_long_limit(true); - - if (msr_short != -1) - m_Limits[PowerType.MsrFast] = msr_short; - if (msr_long != -1) - m_Limits[PowerType.MsrSlow] = msr_long; - - // read value(s) - var value_short = 0; - var value_long = 0; - - while (value_short == 0) - value_short = platform.get_short_value(); - - while (value_long == 0) - value_long = platform.get_long_value(); - - m_Values[PowerType.Fast] = value_short; - m_Values[PowerType.Slow] = value_long; - - // read gfx_clk - var gfx_clk = platform.get_gfx_clk(); - - if (gfx_clk != -1) - m_Misc["gfx_clk"] = gfx_clk; - - base.UpdateTimer_Elapsed(sender, e); - - Monitor.Exit(IsBusy); } } public override void SetTDPLimit(PowerType type, double limit, bool immediate, int result) { - if (Monitor.TryEnter(IsBusy)) + lock (updateLock) { var error = 0; @@ -132,8 +58,6 @@ public override void SetTDPLimit(PowerType type, double limit, bool immediate, i } base.SetTDPLimit(type, limit, immediate, error); - - Monitor.Exit(IsBusy); } } @@ -144,13 +68,11 @@ public void SetMSRLimit(double PL1, double PL2) public override void SetGPUClock(double clock, int result) { - if (Monitor.TryEnter(IsBusy)) + lock (updateLock) { var error = platform.set_gfx_clk((int)clock); base.SetGPUClock(clock, error); - - Monitor.Exit(IsBusy); } } } \ No newline at end of file diff --git a/HandheldCompanion/Processors/Processor.cs b/HandheldCompanion/Processors/Processor.cs index b0a06ad5c..2a2467ab6 100644 --- a/HandheldCompanion/Processors/Processor.cs +++ b/HandheldCompanion/Processors/Processor.cs @@ -1,5 +1,4 @@ using HandheldCompanion.Managers; -using System.Collections.Generic; using System.Timers; namespace HandheldCompanion.Processors; @@ -22,18 +21,9 @@ public class Processor protected readonly Timer updateTimer = new() { Interval = 3000, AutoReset = true }; public bool CanChangeTDP, CanChangeGPU; - protected object IsBusy = new(); + protected object updateLock = new(); public bool IsInitialized; - protected Dictionary m_Limits = new(); - - protected Dictionary m_Misc = new(); - protected Dictionary m_PrevLimits = new(); - protected Dictionary m_PrevMisc = new(); - protected Dictionary m_PrevValues = new(); - - protected Dictionary m_Values = new(); - protected static string Name, ProcessorID; static Processor() @@ -56,9 +46,10 @@ public static Processor GetCurrent() case "AuthenticAMD": processor = new AMDProcessor(); break; + default: + LogManager.LogError("Failed to retrieve processor family: {0}", Manufacturer); + break; } - // write default miscs - processor.m_Misc["gfx_clk"] = processor.m_PrevMisc["gfx_clk"] = 0; return processor; } @@ -67,21 +58,10 @@ public virtual void Initialize() { StatusChanged?.Invoke(CanChangeTDP, CanChangeGPU); Initialized?.Invoke(this); - - // deprecated, we're using LibreHardwareMonitor to provide values and limits - /* - if (CanChangeTDP) - updateTimer.Start(); - */ } public virtual void Stop() { - // deprecated, we're using LibreHardwareMonitor to provide values and limits - /* - if (CanChangeTDP) - updateTimer.Stop(); - */ } public virtual void SetTDPLimit(PowerType type, double limit, bool immediate = false, int result = 0) @@ -103,56 +83,8 @@ public virtual void SetGPUClock(double clock, int result = 0) LogManager.LogDebug("User requested GPU clock: {0}, error code: {1}", clock, result); } - protected virtual void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e) - { - // search for limit changes - foreach (var pair in m_Limits) - { - if (m_PrevLimits[pair.Key] == pair.Value) - continue; - - LimitChanged?.Invoke(pair.Key, pair.Value); - - m_PrevLimits[pair.Key] = pair.Value; - } - - // search for value changes - foreach (var pair in m_Values) - { - if (m_PrevValues[pair.Key] == pair.Value) - continue; - - ValueChanged?.Invoke(pair.Key, pair.Value); - - m_PrevValues[pair.Key] = pair.Value; - } - - // search for misc changes - foreach (var pair in m_Misc) - { - if (m_PrevMisc[pair.Key] == pair.Value) - continue; - - MiscChanged?.Invoke(pair.Key, pair.Value); - - m_PrevMisc[pair.Key] = pair.Value; - } - } - #region events - public event LimitChangedHandler LimitChanged; - - public delegate void LimitChangedHandler(PowerType type, int limit); - - public event ValueChangedHandler ValueChanged; - - public delegate void ValueChangedHandler(PowerType type, float value); - - public event GfxChangedHandler MiscChanged; - - public delegate void GfxChangedHandler(string misc, float value); - public event StatusChangedHandler StatusChanged; public delegate void StatusChangedHandler(bool CanChangeTDP, bool CanChangeGPU); diff --git a/HandheldCompanion/UI/UISounds.cs b/HandheldCompanion/UI/UISounds.cs index 5547bb53c..7849018b8 100644 --- a/HandheldCompanion/UI/UISounds.cs +++ b/HandheldCompanion/UI/UISounds.cs @@ -54,6 +54,9 @@ private static async void SoundTimer_Elapsed(object? sender, ElapsedEventArgs e) { using (WaveOutEvent waveOut = new WaveOutEvent()) { + if (waveOut.DeviceNumber == -1) + return; + waveOut.Init(waveReader); waveOut.Play(); diff --git a/HandheldCompanion/Utils/CrossThreadLock.cs b/HandheldCompanion/Utils/CrossThreadLock.cs index 8d5abfc4e..6f8365bfe 100644 --- a/HandheldCompanion/Utils/CrossThreadLock.cs +++ b/HandheldCompanion/Utils/CrossThreadLock.cs @@ -72,5 +72,10 @@ public void Dispose() _semaphore.Dispose(); GC.SuppressFinalize(this); } + + public bool IsEntered() + { + return _isEntered; + } } } diff --git a/HandheldCompanion/Utils/WPFUtils.cs b/HandheldCompanion/Utils/WPFUtils.cs index e3ba97ae6..322c0665c 100644 --- a/HandheldCompanion/Utils/WPFUtils.cs +++ b/HandheldCompanion/Utils/WPFUtils.cs @@ -1,375 +1,375 @@ -using iNKORE.UI.WPF.Modern.Controls; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Interop; -using System.Windows.Media; -using Control = System.Windows.Controls.Control; - -namespace HandheldCompanion.Utils; - -public static class WPFUtils -{ - public const int WM_KEYDOWN = 0x0100; - public const int WM_CHANGEUISTATE = 0x0127; - public const int UIS_SET = 1; - public const int UIS_CLEAR = 2; - public const int UISF_HIDEFOCUS = 0x1; - - [DllImport("user32.dll")] - public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); - - public static HwndSource GetControlHandle(Control control) - { - return PresentationSource.FromVisual(control) as HwndSource; - } - - public static void MakeFocusVisible(Control c) - { - IntPtr hWnd = GetControlHandle(c).Handle; - // SendMessage(hWnd, WM_CHANGEUISTATE, (IntPtr)MakeLong((int)UIS_CLEAR, (int)UISF_HIDEFOCUS), IntPtr.Zero); - SendMessage(hWnd, 257, (IntPtr)0x0000000000000009, (IntPtr)0x00000000c00f0001); - } - - public static void MakeFocusInvisible(Control c) - { - IntPtr hWnd = GetControlHandle(c).Handle; - SendMessage(hWnd, WM_CHANGEUISTATE, (IntPtr)MakeLong((int)UIS_SET, (int)UISF_HIDEFOCUS), IntPtr.Zero); - } - - public static int MakeLong(int wLow, int wHigh) - { - int low = (int)IntLoWord(wLow); - short high = IntLoWord(wHigh); - int product = 0x10000 * (int)high; - int mkLong = (int)(low | product); - return mkLong; - } - - private static short IntLoWord(int word) - { - return (short)(word & short.MaxValue); - } - - // A function that takes a list of controls and returns the top-left control - public static Control GetTopLeftControl(List controls) where T : Control - { - // filter list - controls = controls.Where(c => c is T).ToList(); - - // If no controls are found, return null - if (controls == null || controls.Count == 0) - { - return null; - } - - // Initialize the top left control with the first element of the list - Control topLeft = controls[0]; - - // Browse other list items - for (int i = 1; i < controls.Count; i++) - { - // Get current control - Control current = controls[i]; - - // Compare the Canvas.Top and Canvas.Left properties of the current control with those of the top-left control - // If the current control is farther up or to the left, replace it with the farthest control to the left - if (Canvas.GetTop(current) < Canvas.GetTop(topLeft) || (Canvas.GetTop(current) == Canvas.GetTop(topLeft) && Canvas.GetLeft(current) < Canvas.GetLeft(topLeft))) - { - topLeft = current; - } - } - - // Return the top left control - return topLeft; - } - - public enum Direction { None, Left, Right, Up, Down } - - public static Control GetClosestControl(Control source, List controls, Direction direction, List typesToIgnore = null) where T : Control - { - // Filter list based on requested type - controls = controls.Where(c => c is T).ToList(); - - // Filter based on exclusion type list - if (typesToIgnore is not null) - controls = controls.Where(c => !typesToIgnore.Contains(c.GetType())).ToList(); - - // Filter out the controls that are not in the given direction - controls = controls.Where(c => c != source && IsInDirection(source, c, direction)).ToList(); - - // If no controls are found, return source - if (controls.Count == 0) return source; - - // Find the control with the same parent and the minimum distance to the source - // If no control has the same parent, find the control with the minimum distance to the source - controls = controls.OrderBy(c => GetDistanceV2(source, c, direction)).ToList(); - - return controls.First(); - } - - // Helper method to check if a control is in a given direction from another control - private static bool IsInDirection(Control source, Control target, Direction direction) - { - // Get the position of the target on the canvas - var p = target.TranslatePoint(new Point(0, 0), source); - double x = Math.Round(p.X); - double y = Math.Round(p.Y); - - switch (direction) - { - case Direction.Left: - return x + (target.ActualWidth / 2) <= 0; - case Direction.Right: - return x >= (source.ActualWidth / 2); - case Direction.Up: - return y + (target.ActualHeight / 2) <= 0; - case Direction.Down: - return y >= (source.ActualHeight / 2); - default: - return false; - } - } - - // Helper method to calculate the distance between the centers of two controls - private static double GetDistance(Control source, Control target, Direction direction) - { - try - { - // Get the relative position of the target with respect to the source - var transform = target.TransformToVisual(source); - var position = transform.Transform(new Point(0, 0)); - - double dx = source.ActualWidth / 2 - (position.X + target.ActualWidth / 2); - double dy = source.ActualHeight / 2 - (position.Y + target.ActualHeight / 2); - - switch (direction) - { - case Direction.Up: - case Direction.Down: - return Math.Sqrt(dy * dy); - - case Direction.Left: - case Direction.Right: - return Math.Sqrt(dx * dx); - } - - return Math.Sqrt(dx * dx + dy * dy); - } - catch { } - - return 9999.0d; - } - - public static double GetDistanceV2(Control c1, Control c2, Direction direction) - { - try - { - // We retrieve the control's bounding box - Rect r1 = c1.TransformToVisual(c1).TransformBounds(new Rect(c1.RenderSize)); - Rect r2 = c2.TransformToVisual(c1).TransformBounds(new Rect(c2.RenderSize)); - - // Calculate the horizontal and vertical distances between the edges of the rectangles - double dx = Math.Max(0, Math.Max(r1.Left, r2.Left) - Math.Min(r1.Right, r2.Right)); - double dy = Math.Max(0, Math.Max(r1.Top, r2.Top) - Math.Min(r1.Bottom, r2.Bottom)); - - // Return the Euclidean distance between the nearest edges - return Math.Sqrt(dx * dx + dy * dy); - } - catch { } - - return 9999.0d; - } - - // This function takes two controls and returns their distance in pixels - private static double GetDistanceV3(Control c1, Control c2, Direction direction) - { - try - { - // Get the position of each control relative to the screen - Point p1 = c1.PointToScreen(new Point(0, 0)); - Point p2 = c2.PointToScreen(new Point(0, 0)); - - // Convert the points to vectors - Vector3 v1 = new Vector3((float)p1.X, (float)p1.Y, 0f); - Vector3 v2 = new Vector3((float)p2.X, (float)p2.Y, 0f); - - switch (direction) - { - case Direction.Up: - case Direction.Down: - v1 = new Vector3(0f, (float)p1.Y, 0f); - v2 = new Vector3(0f, (float)p2.Y, 0f); - break; - - case Direction.Left: - case Direction.Right: - v1 = new Vector3((float)p1.X, 0f, 0f); - v2 = new Vector3((float)p2.X, 0f, 0f); - break; - } - - // Calculate and return the distance between the vectors - return Vector3.Distance(v1, v2); - } - catch { } - - return 9999.0d; - } - - public static List FindChildren(DependencyObject startNode) - { - int count = VisualTreeHelper.GetChildrenCount(startNode); - List childs = new(); - - for (int i = 0; i < count; i++) - { - DependencyObject current = VisualTreeHelper.GetChild(startNode, i); - - string currentType = current.GetType().Name; - switch (currentType) - { - case "TextBox": - { - TextBox textBox = (TextBox)current; - if (!textBox.IsReadOnly) - goto case "Slider"; - } - break; - - case "RepeatButton": - { - RepeatButton repeatButton = (RepeatButton)current; - if (!repeatButton.Name.StartsWith("PART_")) - { - // skip if repeat button is part of scrollbar - goto case "Slider"; - } - } - break; - - case "Button": - { - Button button = (Button)current; - if (button.Name.Equals("NavigationViewBackButton")) - break; - else if (button.Name.Equals("TogglePaneButton")) - break; - else - goto case "Slider"; - } - break; - - case "Slider": - case "ToggleSwitch": - case "NavigationViewItem": - case "ComboBox": - case "ComboBoxItem": - case "AppBarButton": - case "ToggleButton": - case "CheckBox": - case "RadioButton": - { - FrameworkElement asType = (FrameworkElement)current; - if (asType.IsEnabled && asType.Focusable && asType.IsVisible) - childs.Add(asType); - } - break; - } - - foreach (var item in FindChildren(current)) - { - childs.Add(item); - } - } - - return childs; - } - - public static T FindParent(DependencyObject child) where T : DependencyObject - { - T parent = null; - if (child is null) - { - return parent; - } - DependencyObject CurrentParent = VisualTreeHelper.GetParent(child); - while (CurrentParent is not null) - { - if (CurrentParent is T) - { - parent = (T)CurrentParent; - break; - } - CurrentParent = VisualTreeHelper.GetParent(CurrentParent); - } - return parent; - } - - // Helper method to find all visual children of a given type - public static IEnumerable FindVisualChildren(DependencyObject parent) where T : DependencyObject - { - if (parent != null) - { - for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) - { - var child = VisualTreeHelper.GetChild(parent, i); - - if (child is T) - { - yield return (T)child; - } - - foreach (var childOfChild in FindVisualChildren(child)) - { - yield return childOfChild; - } - } - } - } - - // Returns all FrameworkElement of specified type from a list, where their parent or parents of their parent is oftype() Popup - public static List GetElementsFromPopup(List elements) where T : FrameworkElement - { - // Create an empty list to store the result - List result = new List(); - - // Loop through each element in the input list - foreach (FrameworkElement element in elements) - { - // Check if the element is of the specified type - if (element is T) - { - // Get the parent of the element - FrameworkElement parent = element.Parent as FrameworkElement; - - // Loop until the parent is null or a Popup - while (parent != null && (!(parent is Popup) && !(parent is ContentDialog))) - { - // Get the parent of the parent - parent = parent.Parent as FrameworkElement; - } - - // Check if the parent is a Popup - if (parent is Popup || parent is ContentDialog) - { - // Add the element to the result list - result.Add(element as T); - } - } - } - - // Return the result list - return result; - } - - public static void SendKeyToControl(Control control, int keyCode) - { - SendMessage(GetControlHandle(control).Handle.ToInt32(), WM_KEYDOWN, keyCode, IntPtr.Zero); - } +using iNKORE.UI.WPF.Modern.Controls; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Interop; +using System.Windows.Media; +using Control = System.Windows.Controls.Control; + +namespace HandheldCompanion.Utils; + +public static class WPFUtils +{ + public const int WM_KEYDOWN = 0x0100; + public const int WM_CHANGEUISTATE = 0x0127; + public const int UIS_SET = 1; + public const int UIS_CLEAR = 2; + public const int UISF_HIDEFOCUS = 0x1; + + [DllImport("user32.dll")] + public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); + + public static HwndSource GetControlHandle(Control control) + { + return PresentationSource.FromVisual(control) as HwndSource; + } + + public static void MakeFocusVisible(Control c) + { + IntPtr hWnd = GetControlHandle(c).Handle; + // SendMessage(hWnd, WM_CHANGEUISTATE, (IntPtr)MakeLong((int)UIS_CLEAR, (int)UISF_HIDEFOCUS), IntPtr.Zero); + SendMessage(hWnd, 257, (IntPtr)0x0000000000000009, (IntPtr)0x00000000c00f0001); + } + + public static void MakeFocusInvisible(Control c) + { + IntPtr hWnd = GetControlHandle(c).Handle; + SendMessage(hWnd, WM_CHANGEUISTATE, (IntPtr)MakeLong((int)UIS_SET, (int)UISF_HIDEFOCUS), IntPtr.Zero); + } + + public static int MakeLong(int wLow, int wHigh) + { + int low = (int)IntLoWord(wLow); + short high = IntLoWord(wHigh); + int product = 0x10000 * (int)high; + int mkLong = (int)(low | product); + return mkLong; + } + + private static short IntLoWord(int word) + { + return (short)(word & short.MaxValue); + } + + // A function that takes a list of controls and returns the top-left control + public static Control GetTopLeftControl(List controls) where T : Control + { + // filter list + controls = controls.Where(c => c is T).ToList(); + + // If no controls are found, return null + if (controls == null || controls.Count == 0) + { + return null; + } + + // Initialize the top left control with the first element of the list + Control topLeft = controls[0]; + + // Browse other list items + for (int i = 1; i < controls.Count; i++) + { + // Get current control + Control current = controls[i]; + + // Compare the Canvas.Top and Canvas.Left properties of the current control with those of the top-left control + // If the current control is farther up or to the left, replace it with the farthest control to the left + if (Canvas.GetTop(current) < Canvas.GetTop(topLeft) || (Canvas.GetTop(current) == Canvas.GetTop(topLeft) && Canvas.GetLeft(current) < Canvas.GetLeft(topLeft))) + { + topLeft = current; + } + } + + // Return the top left control + return topLeft; + } + + public enum Direction { None, Left, Right, Up, Down } + + public static Control GetClosestControl(Control source, List controls, Direction direction, List typesToIgnore = null) where T : Control + { + // Filter list based on requested type + controls = controls.Where(c => c is T).ToList(); + + // Filter based on exclusion type list + if (typesToIgnore is not null) + controls = controls.Where(c => !typesToIgnore.Contains(c.GetType())).ToList(); + + // Filter out the controls that are not in the given direction + controls = controls.Where(c => c != source && IsInDirection(source, c, direction)).ToList(); + + // If no controls are found, return source + if (controls.Count == 0) return source; + + // Find the control with the same parent and the minimum distance to the source + // If no control has the same parent, find the control with the minimum distance to the source + controls = controls.OrderBy(c => GetDistanceV2(source, c, direction)).ToList(); + + return controls.First(); + } + + // Helper method to check if a control is in a given direction from another control + private static bool IsInDirection(Control source, Control target, Direction direction) + { + // Get the position of the target on the canvas + var p = target.TranslatePoint(new Point(0, 0), source); + double x = Math.Round(p.X); + double y = Math.Round(p.Y); + + switch (direction) + { + case Direction.Left: + return x + (target.ActualWidth / 2) <= 0; + case Direction.Right: + return x >= (source.ActualWidth / 2); + case Direction.Up: + return y + (target.ActualHeight / 2) <= 0; + case Direction.Down: + return y >= (source.ActualHeight / 2); + default: + return false; + } + } + + // Helper method to calculate the distance between the centers of two controls + private static double GetDistance(Control source, Control target, Direction direction) + { + try + { + // Get the relative position of the target with respect to the source + var transform = target.TransformToVisual(source); + var position = transform.Transform(new Point(0, 0)); + + double dx = source.ActualWidth / 2 - (position.X + target.ActualWidth / 2); + double dy = source.ActualHeight / 2 - (position.Y + target.ActualHeight / 2); + + switch (direction) + { + case Direction.Up: + case Direction.Down: + return Math.Sqrt(dy * dy); + + case Direction.Left: + case Direction.Right: + return Math.Sqrt(dx * dx); + } + + return Math.Sqrt(dx * dx + dy * dy); + } + catch { } + + return 9999.0d; + } + + public static double GetDistanceV2(Control c1, Control c2, Direction direction) + { + try + { + // We retrieve the control's bounding box + Rect r1 = c1.TransformToVisual(c1).TransformBounds(new Rect(c1.RenderSize)); + Rect r2 = c2.TransformToVisual(c1).TransformBounds(new Rect(c2.RenderSize)); + + // Calculate the horizontal and vertical distances between the edges of the rectangles + double dx = Math.Max(0, Math.Max(r1.Left, r2.Left) - Math.Min(r1.Right, r2.Right)); + double dy = Math.Max(0, Math.Max(r1.Top, r2.Top) - Math.Min(r1.Bottom, r2.Bottom)); + + // Return the Euclidean distance between the nearest edges + return Math.Sqrt(dx * dx + dy * dy); + } + catch { } + + return 9999.0d; + } + + // This function takes two controls and returns their distance in pixels + private static double GetDistanceV3(Control c1, Control c2, Direction direction) + { + try + { + // Get the position of each control relative to the screen + Point p1 = c1.PointToScreen(new Point(0, 0)); + Point p2 = c2.PointToScreen(new Point(0, 0)); + + // Convert the points to vectors + Vector3 v1 = new Vector3((float)p1.X, (float)p1.Y, 0f); + Vector3 v2 = new Vector3((float)p2.X, (float)p2.Y, 0f); + + switch (direction) + { + case Direction.Up: + case Direction.Down: + v1 = new Vector3(0f, (float)p1.Y, 0f); + v2 = new Vector3(0f, (float)p2.Y, 0f); + break; + + case Direction.Left: + case Direction.Right: + v1 = new Vector3((float)p1.X, 0f, 0f); + v2 = new Vector3((float)p2.X, 0f, 0f); + break; + } + + // Calculate and return the distance between the vectors + return Vector3.Distance(v1, v2); + } + catch { } + + return 9999.0d; + } + + public static List FindChildren(DependencyObject startNode) + { + int count = VisualTreeHelper.GetChildrenCount(startNode); + List childs = new(); + + for (int i = 0; i < count; i++) + { + DependencyObject current = VisualTreeHelper.GetChild(startNode, i); + + string currentType = current.GetType().Name; + switch (currentType) + { + case "TextBox": + { + TextBox textBox = (TextBox)current; + if (!textBox.IsReadOnly) + goto case "Slider"; + } + break; + + case "RepeatButton": + { + RepeatButton repeatButton = (RepeatButton)current; + if (!repeatButton.Name.StartsWith("PART_")) + { + // skip if repeat button is part of scrollbar + goto case "Slider"; + } + } + break; + + case "Button": + { + Button button = (Button)current; + if (button.Name.Equals("NavigationViewBackButton")) + break; + else if (button.Name.Equals("TogglePaneButton")) + break; + else + goto case "Slider"; + } + break; + + case "Slider": + case "ToggleSwitch": + case "NavigationViewItem": + case "ComboBox": + case "ComboBoxItem": + case "AppBarButton": + case "ToggleButton": + case "CheckBox": + case "RadioButton": + { + FrameworkElement asType = (FrameworkElement)current; + if (asType.IsEnabled && asType.Focusable && asType.IsVisible) + childs.Add(asType); + } + break; + } + + foreach (var item in FindChildren(current)) + { + childs.Add(item); + } + } + + return childs; + } + + public static T FindParent(DependencyObject child) where T : DependencyObject + { + T parent = null; + if (child is null) + { + return parent; + } + DependencyObject CurrentParent = VisualTreeHelper.GetParent(child); + while (CurrentParent is not null) + { + if (CurrentParent is T) + { + parent = (T)CurrentParent; + break; + } + CurrentParent = VisualTreeHelper.GetParent(CurrentParent); + } + return parent; + } + + // Helper method to find all visual children of a given type + public static IEnumerable FindVisualChildren(DependencyObject parent) where T : DependencyObject + { + if (parent != null) + { + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + + if (child is T) + { + yield return (T)child; + } + + foreach (var childOfChild in FindVisualChildren(child)) + { + yield return childOfChild; + } + } + } + } + + // Returns all FrameworkElement of specified type from a list, where their parent or parents of their parent is oftype() Popup + public static List GetElementsFromPopup(List elements) where T : FrameworkElement + { + // Create an empty list to store the result + List result = new List(); + + // Loop through each element in the input list + foreach (FrameworkElement element in elements) + { + // Check if the element is of the specified type + if (element is T) + { + // Get the parent of the element + FrameworkElement parent = element.Parent as FrameworkElement; + + // Loop until the parent is null or a Popup + while (parent != null && (!(parent is Popup) && !(parent is ContentDialog))) + { + // Get the parent of the parent + parent = parent.Parent as FrameworkElement; + } + + // Check if the parent is a Popup + if (parent is Popup || parent is ContentDialog) + { + // Add the element to the result list + result.Add(element as T); + } + } + } + + // Return the result list + return result; + } + + public static void SendKeyToControl(Control control, int keyCode) + { + SendMessage(GetControlHandle(control).Handle.ToInt32(), WM_KEYDOWN, keyCode, IntPtr.Zero); + } } \ No newline at end of file diff --git a/HandheldCompanion/Views/Classes/GamepadWindow.cs b/HandheldCompanion/Views/Classes/GamepadWindow.cs index 880ea342e..c6f8e2b5a 100644 --- a/HandheldCompanion/Views/Classes/GamepadWindow.cs +++ b/HandheldCompanion/Views/Classes/GamepadWindow.cs @@ -1,112 +1,112 @@ -using HandheldCompanion.Managers; -using HandheldCompanion.Utils; -using iNKORE.UI.WPF.Modern.Controls; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace HandheldCompanion.Views.Classes -{ - public class GamepadWindow : Window - { - public List controlElements = new(); - public List frameworkElements = new(); - - public ContentDialog currentDialog; - protected UIGamepad gamepadFocusManager; - - public GamepadWindow() - { - LayoutUpdated += OnLayoutUpdated; - } - - protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) - { - // Track when objects are added and removed - if (visualAdded != null && visualAdded is Control) - controlElements.Add((Control)visualAdded); - - if (visualRemoved != null && visualRemoved is Control) - controlElements.Remove((Control)visualRemoved); - - base.OnVisualChildrenChanged(visualAdded, visualRemoved); - } - - public ScrollViewer GetScrollViewer(DependencyObject depObj) - { - if (depObj is ScrollViewer) { return depObj as ScrollViewer; } - - for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) - { - var child = VisualTreeHelper.GetChild(depObj, i); - var result = GetScrollViewer(child); - if (result != null && result.Name.Equals("scrollViewer")) - return result; - } - return null; - } - - private void OnLayoutUpdated(object? sender, EventArgs e) - { - if (!this.IsActive || this.Visibility != Visibility.Visible) - return; - - // get all FrameworkElement(s) - frameworkElements = WPFUtils.FindChildren(this); - - // do we have a popup ? - ContentDialog dialog = ContentDialog.GetOpenDialog(this); - if (dialog is not null) - { - if (currentDialog is null) - { - currentDialog = dialog; - - frameworkElements = WPFUtils.FindChildren(this); - - // get all Control(s) - controlElements = WPFUtils.GetElementsFromPopup(frameworkElements); - - ContentDialogOpened?.Invoke(); - } - } - else if (dialog is null) - { - // get all Control(s) - controlElements = frameworkElements.OfType().ToList(); - - if (currentDialog is not null) - { - currentDialog = null; - } - } - } - - protected void InvokeGotGamepadWindowFocus() - { - GotGamepadWindowFocus?.Invoke(); - } - - protected void InvokeLostGamepadWindowFocus() - { - LostGamepadWindowFocus?.Invoke(); - } - - #region events - public event GotGamepadWindowFocusEventHandler GotGamepadWindowFocus; - public delegate void GotGamepadWindowFocusEventHandler(); - - public event LostGamepadWindowFocusEventHandler LostGamepadWindowFocus; - public delegate void LostGamepadWindowFocusEventHandler(); - - public event ContentDialogOpenedEventHandler ContentDialogOpened; - public delegate void ContentDialogOpenedEventHandler(); - - public event ContentDialogClosedEventHandler ContentDialogClosed; - public delegate void ContentDialogClosedEventHandler(); - #endregion - } -} +using HandheldCompanion.Managers; +using HandheldCompanion.Utils; +using iNKORE.UI.WPF.Modern.Controls; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace HandheldCompanion.Views.Classes +{ + public class GamepadWindow : Window + { + public List controlElements = new(); + public List frameworkElements = new(); + + public ContentDialog currentDialog; + protected UIGamepad gamepadFocusManager; + + public GamepadWindow() + { + LayoutUpdated += OnLayoutUpdated; + } + + protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) + { + // Track when objects are added and removed + if (visualAdded != null && visualAdded is Control) + controlElements.Add((Control)visualAdded); + + if (visualRemoved != null && visualRemoved is Control) + controlElements.Remove((Control)visualRemoved); + + base.OnVisualChildrenChanged(visualAdded, visualRemoved); + } + + public ScrollViewer GetScrollViewer(DependencyObject depObj) + { + if (depObj is ScrollViewer) { return depObj as ScrollViewer; } + + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) + { + var child = VisualTreeHelper.GetChild(depObj, i); + var result = GetScrollViewer(child); + if (result != null && result.Name.Equals("scrollViewer")) + return result; + } + return null; + } + + private void OnLayoutUpdated(object? sender, EventArgs e) + { + if (!this.IsActive || this.Visibility != Visibility.Visible) + return; + + // get all FrameworkElement(s) + frameworkElements = WPFUtils.FindChildren(this); + + // do we have a popup ? + ContentDialog dialog = ContentDialog.GetOpenDialog(this); + if (dialog is not null) + { + if (currentDialog is null) + { + currentDialog = dialog; + + frameworkElements = WPFUtils.FindChildren(this); + + // get all Control(s) + controlElements = WPFUtils.GetElementsFromPopup(frameworkElements); + + ContentDialogOpened?.Invoke(); + } + } + else if (dialog is null) + { + // get all Control(s) + controlElements = frameworkElements.OfType().ToList(); + + if (currentDialog is not null) + { + currentDialog = null; + } + } + } + + protected void InvokeGotGamepadWindowFocus() + { + GotGamepadWindowFocus?.Invoke(); + } + + protected void InvokeLostGamepadWindowFocus() + { + LostGamepadWindowFocus?.Invoke(); + } + + #region events + public event GotGamepadWindowFocusEventHandler GotGamepadWindowFocus; + public delegate void GotGamepadWindowFocusEventHandler(); + + public event LostGamepadWindowFocusEventHandler LostGamepadWindowFocus; + public delegate void LostGamepadWindowFocusEventHandler(); + + public event ContentDialogOpenedEventHandler ContentDialogOpened; + public delegate void ContentDialogOpenedEventHandler(); + + public event ContentDialogClosedEventHandler ContentDialogClosed; + public delegate void ContentDialogClosedEventHandler(); + #endregion + } +} diff --git a/HandheldCompanion/Views/Pages/ControllerPage.xaml b/HandheldCompanion/Views/Pages/ControllerPage.xaml index 6583e9658..f73137d81 100644 --- a/HandheldCompanion/Views/Pages/ControllerPage.xaml +++ b/HandheldCompanion/Views/Pages/ControllerPage.xaml @@ -163,23 +163,25 @@ - - - - - - - + + + + + + + + + - - - - - - - + + + + + + + + + { - SimpleStackPanel targetPanel = Controller.IsVirtual() ? VirtualDevices : PhysicalDevices; + SimpleStackPanel targetPanel = Controller.IsVirtual() ? VirtualDevicesList : PhysicalDevicesList; // Search for an existing controller, remove it foreach (IController ctrl in targetPanel.Children) @@ -134,7 +135,7 @@ private void ControllerPlugged(IController Controller, bool IsPowerCycling) // UI thread (async) Application.Current.Dispatcher.Invoke(() => { - SimpleStackPanel targetPanel = Controller.IsVirtual() ? VirtualDevices : PhysicalDevices; + SimpleStackPanel targetPanel = Controller.IsVirtual() ? VirtualDevicesList : PhysicalDevicesList; // Search for an existing controller, remove it foreach (IController ctrl in targetPanel.Children) @@ -161,22 +162,22 @@ private void ControllerManager_ControllerSelected(IController Controller) ControllerRefresh(); } - private void ControllerManager_Working(int status) + private void ControllerManager_Working(ControllerManagerStatus status) { // UI thread (async) Application.Current.Dispatcher.Invoke(async () => { - // status: 0:wip, 1:sucess, 2:failed switch (status) { - case 0: + case ControllerManagerStatus.Busy: ControllerLoading.Visibility = Visibility.Visible; VirtualDevices.IsEnabled = false; PhysicalDevices.IsEnabled = false; MainGrid.IsEnabled = false; break; - case 1: - case 2: + + case ControllerManagerStatus.Succeeded: + case ControllerManagerStatus.Failed: ControllerLoading.Visibility = Visibility.Hidden; VirtualDevices.IsEnabled = true; PhysicalDevices.IsEnabled = true; @@ -186,8 +187,7 @@ private void ControllerManager_Working(int status) ControllerRefresh(); - // failed - if (status == 2) + if (status == ControllerManagerStatus.Failed) { // todo: translate me Task dialogTask = new Dialog(MainWindow.GetCurrent()) @@ -276,7 +276,7 @@ private void ControllerRefresh() // hint: Has physical controller (not Neptune) hidden, but no virtual controller VirtualDevices.Visibility = hasVirtual ? Visibility.Visible : Visibility.Collapsed; - WarningNoVirtual.Visibility = !hasVirtual ? Visibility.Visible : Visibility.Collapsed; + WarningNoVirtual.Visibility = isHidden && !hasVirtual ? Visibility.Visible : Visibility.Collapsed; // hint: Has physical controller (Neptune) hidden, but virtual controller is muted bool neptunehidden = isHidden && isSteam && isMuted; diff --git a/HandheldCompanion/Views/Pages/LayoutPage.xaml.cs b/HandheldCompanion/Views/Pages/LayoutPage.xaml.cs index f0bf8e113..9eaecf7ef 100644 --- a/HandheldCompanion/Views/Pages/LayoutPage.xaml.cs +++ b/HandheldCompanion/Views/Pages/LayoutPage.xaml.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -26,7 +27,7 @@ public partial class LayoutPage : Page // Getter to update layout in ViewModels public Layout CurrentLayout => currentTemplate.Layout; public LayoutTemplate currentTemplate = new(); - protected LockObject updateLock = new(); + protected object updateLock = new(); // page vars private Dictionary pages; @@ -270,9 +271,9 @@ private void UpdatePages() // This is a very important lock, it blocks backward events to the layout when // this is actually the backend that triggered the update. Notifications on higher // levels (pages and mappings) could potentially be blocked for optimization. - using (new ScopedLock(updateLock)) + lock (updateLock) { - // UI thread (async) + // UI thread Application.Current.Dispatcher.Invoke(() => { // Invoke Layout Updated to trigger ViewModel updates diff --git a/HandheldCompanion/Views/Pages/PerformancePage.xaml b/HandheldCompanion/Views/Pages/PerformancePage.xaml index 6ed554eac..1564fd829 100644 --- a/HandheldCompanion/Views/Pages/PerformancePage.xaml +++ b/HandheldCompanion/Views/Pages/PerformancePage.xaml @@ -1,846 +1,846 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs index 64bc1f610..e4cb458fa 100644 --- a/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs @@ -1,21 +1,21 @@ -using HandheldCompanion.Managers; -using HandheldCompanion.ViewModels; -using System; -using Page = System.Windows.Controls.Page; - -namespace HandheldCompanion.Views.QuickPages; - -public partial class QuickPerformancePage : Page -{ - public QuickPerformancePage() - { - Tag = "quickperformance"; - DataContext = new PerformancePageViewModel(isQuickTools: true); - InitializeComponent(); - } - - public void SelectionChanged(Guid guid) - { - ((PerformancePageViewModel)DataContext).SelectedPreset = PowerProfileManager.GetProfile(guid); - } +using HandheldCompanion.Managers; +using HandheldCompanion.ViewModels; +using System; +using Page = System.Windows.Controls.Page; + +namespace HandheldCompanion.Views.QuickPages; + +public partial class QuickPerformancePage : Page +{ + public QuickPerformancePage() + { + Tag = "quickperformance"; + DataContext = new PerformancePageViewModel(isQuickTools: true); + InitializeComponent(); + } + + public void SelectionChanged(Guid guid) + { + ((PerformancePageViewModel)DataContext).SelectedPreset = PowerProfileManager.GetProfile(guid); + } } \ No newline at end of file diff --git a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs index 6b9307940..09d9c1afc 100644 --- a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs @@ -533,13 +533,6 @@ private void ProfileManager_Deleted(Profile profile) ProcessManager_ForegroundChanged(currentProcess, null); } - private void ProfileManager_Deleted(Profile profile) - { - // this shouldn't happen, someone removed the currently applied profile - if (selectedProfile == profile) - ProcessManager_ForegroundChanged(currentProcess, null); - } - private void ProcessManager_ForegroundChanged(ProcessEx processEx, ProcessEx backgroundEx) { if (foregroundLock.TryEnter()) diff --git a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs index 14b97abd9..9b4e83d58 100644 --- a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs +++ b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs @@ -1,239 +1,237 @@ -using HandheldCompanion.Controllers; -using HandheldCompanion.Devices; -using HandheldCompanion.Inputs; -using HandheldCompanion.Managers; -using HandheldCompanion.Utils; -using HandheldCompanion.Views.Classes; -using HandheldCompanion.Views.Pages; -using HandheldCompanion.Views.Windows; -using iNKORE.UI.WPF.Modern.Controls; -using Nefarius.Utilities.DeviceManagement.PnP; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Forms; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Navigation; -using Windows.UI.ViewManagement; -using static HandheldCompanion.Managers.InputsHotkey; -using Application = System.Windows.Application; -using Control = System.Windows.Controls.Control; -using Page = System.Windows.Controls.Page; -using RadioButton = System.Windows.Controls.RadioButton; - -namespace HandheldCompanion.Views; - -/// -/// Interaction logic for MainWindow.xaml -/// -public partial class MainWindow : GamepadWindow -{ - // devices vars - public static IDevice CurrentDevice; - - // page vars - private static readonly Dictionary _pages = new(); - - public static ControllerPage controllerPage; - public static DevicePage devicePage; - public static PerformancePage performancePage; - public static ProfilesPage profilesPage; - public static SettingsPage settingsPage; - public static AboutPage aboutPage; - public static OverlayPage overlayPage; - public static HotkeysPage hotkeysPage; - public static LayoutPage layoutPage; - public static NotificationsPage notificationsPage; - - // overlay(s) vars - public static OverlayModel overlayModel; - public static OverlayTrackpad overlayTrackpad; - public static OverlayQuickTools overlayquickTools; - - public static string CurrentExe, CurrentPath; - - private static MainWindow CurrentWindow; - public static FileVersionInfo fileVersionInfo; - - public static string InstallPath = string.Empty; - public static string SettingsPath = string.Empty; - public static string CurrentPageName = string.Empty; - - private bool appClosing; - private bool IsReady; - private readonly NotifyIcon notifyIcon; - private bool NotifyInTaskbar; - private string preNavItemTag; - - private WindowState prevWindowState; - private SplashScreen splashScreen; - - public static UISettings uiSettings; - - private const int WM_QUERYENDSESSION = 0x0011; - - public MainWindow(FileVersionInfo _fileVersionInfo, Assembly CurrentAssembly) - { - InitializeComponent(); - - fileVersionInfo = _fileVersionInfo; - CurrentWindow = this; - - // used by system manager, controller manager - uiSettings = new UISettings(); - - // used by gamepad navigation - Tag = "MainWindow"; - - // get process - var process = Process.GetCurrentProcess(); - - // fix touch support - TabletDeviceCollection tabletDevices = Tablet.TabletDevices; - /*if (tabletDevices.Count > 0) - { - // Get the Type of InputManager. - Type inputManagerType = typeof(System.Windows.Input.InputManager); - - // Call the StylusLogic method on the InputManager.Current instance. - object stylusLogic = inputManagerType.InvokeMember("StylusLogic", - BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic, - null, InputManager.Current, null); - - if (stylusLogic != null) - { - // Get the type of the stylusLogic returned from the call to StylusLogic. - Type stylusLogicType = stylusLogic.GetType(); - - // Loop until there are no more devices to remove. - while (tabletDevices.Count > 0) - { - // Remove the first tablet device in the devices collection. - stylusLogicType.InvokeMember("OnTabletRemoved", - BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, - null, stylusLogic, new object[] { (uint)0 }); - } - } - }*/ +using HandheldCompanion.Controllers; +using HandheldCompanion.Devices; +using HandheldCompanion.Inputs; +using HandheldCompanion.Managers; +using HandheldCompanion.UI; +using HandheldCompanion.Utils; +using HandheldCompanion.Views.Classes; +using HandheldCompanion.Views.Pages; +using HandheldCompanion.Views.Windows; +using iNKORE.UI.WPF.Modern.Controls; +using Nefarius.Utilities.DeviceManagement.PnP; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Forms; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Navigation; +using System.Windows.Threading; +using Windows.UI.ViewManagement; +using static HandheldCompanion.Managers.InputsHotkey; +using Application = System.Windows.Application; +using Control = System.Windows.Controls.Control; +using Page = System.Windows.Controls.Page; +using RadioButton = System.Windows.Controls.RadioButton; + +namespace HandheldCompanion.Views; + +/// +/// Interaction logic for MainWindow.xaml +/// +public partial class MainWindow : GamepadWindow +{ + // devices vars + private static IDevice CurrentDevice; + + // page vars + private static readonly Dictionary _pages = new(); + + public static ControllerPage controllerPage; + public static DevicePage devicePage; + public static PerformancePage performancePage; + public static ProfilesPage profilesPage; + public static SettingsPage settingsPage; + public static AboutPage aboutPage; + public static OverlayPage overlayPage; + public static HotkeysPage hotkeysPage; + public static LayoutPage layoutPage; + public static NotificationsPage notificationsPage; + + // overlay(s) vars + public static OverlayModel overlayModel; + public static OverlayTrackpad overlayTrackpad; + public static OverlayQuickTools overlayquickTools; + + public static string CurrentExe, CurrentPath; + + private static MainWindow CurrentWindow; + public static FileVersionInfo fileVersionInfo; + + public static string InstallPath = string.Empty; + public static string SettingsPath = string.Empty; + public static string CurrentPageName = string.Empty; + + private bool appClosing; + private readonly NotifyIcon notifyIcon; + private bool NotifyInTaskbar; + private string preNavItemTag; + + private WindowState prevWindowState; + public static SplashScreen SplashScreen; + + public static UISettings uiSettings; + + private const int WM_QUERYENDSESSION = 0x0011; + private const int WM_DISPLAYCHANGE = 0x007e; + private const int WM_DEVICECHANGE = 0x0219; + + public MainWindow(FileVersionInfo _fileVersionInfo, Assembly CurrentAssembly) + { + // initialize splash screen + SplashScreen = new SplashScreen(); // get first start - bool FirstStart = SettingsManager.GetBoolean("FirstStart"); - - // define current directory - InstallPath = AppDomain.CurrentDomain.BaseDirectory; - SettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - "HandheldCompanion"); - - // initialiaze path - if (!Directory.Exists(SettingsPath)) - Directory.CreateDirectory(SettingsPath); - - Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); - - // initialize XInputWrapper - XInputPlus.ExtractXInputPlusLibraries(); - - // initialize notifyIcon - notifyIcon = new NotifyIcon + bool FirstStart = SettingsManager.GetBoolean("FirstStart"); + + if (FirstStart) { - Text = Title, - Icon = System.Drawing.Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location), - Visible = false, - ContextMenuStrip = new ContextMenuStrip() - }; - - notifyIcon.DoubleClick += (sender, e) => { SwapWindowState(); }; - - AddNotifyIconItem(Properties.Resources.MainWindow_MainWindow); +#if !DEBUG + SplashScreen.Show(); +#endif + } + + SplashScreen.LoadingSequence.Text = "Preparing UI..."; + + InitializeComponent(); + this.Tag = "MainWindow"; + + fileVersionInfo = _fileVersionInfo; + CurrentWindow = this; + + // used by system manager, controller manager + uiSettings = new UISettings(); + + // fix touch support + TabletDeviceCollection tabletDevices = Tablet.TabletDevices; + + // define current directory + InstallPath = AppDomain.CurrentDomain.BaseDirectory; + SettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + "HandheldCompanion"); + + // initialiaze path + if (!Directory.Exists(SettingsPath)) + Directory.CreateDirectory(SettingsPath); + + Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); + + // initialize XInputWrapper + XInputPlus.ExtractXInputPlusLibraries(); + + // initialize notifyIcon + notifyIcon = new NotifyIcon + { + Text = Title, + Icon = System.Drawing.Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location), + Visible = false, + ContextMenuStrip = new ContextMenuStrip() + }; + + notifyIcon.DoubleClick += (sender, e) => { SwapWindowState(); }; + + AddNotifyIconItem(Properties.Resources.MainWindow_MainWindow); AddNotifyIconItem(Properties.Resources.MainWindow_QuickTools); - - AddNotifyIconSeparator(); - AddNotifyIconItem(Properties.Resources.MainWindow_Exit); - - // paths - CurrentExe = process.MainModule.FileName; - CurrentPath = AppDomain.CurrentDomain.BaseDirectory; - - // initialize HidHide + AddNotifyIconSeparator(); + + AddNotifyIconItem(Properties.Resources.MainWindow_Exit); + + // paths + Process process = Process.GetCurrentProcess(); + CurrentExe = process.MainModule.FileName; + CurrentPath = AppDomain.CurrentDomain.BaseDirectory; + + // initialize HidHide HidHide.RegisterApplication(CurrentExe); - // initialize title - Title += $" ({fileVersionInfo.FileVersion})"; - - // initialize device - CurrentDevice = IDevice.GetDefault(); + // collect details from MotherboardInfo + MotherboardInfo.Collect(); + + // initialize title + Title += $" ({fileVersionInfo.FileVersion})"; + + // initialize device + SplashScreen.LoadingSequence.Text = "Initializing device..."; + CurrentDevice = IDevice.GetCurrent(); CurrentDevice.PullSensors(); - // workaround for Bosch BMI320/BMI323 (as of 06/20/2023) - // todo: check if still needed with Bosch G-sensor Driver V1.0.1.7 - // https://dlcdnets.asus.com/pub/ASUS/IOTHMD/Image/Driver/Chipset/34644/BoschG-sensor_ROG_Bosch_Z_V1.0.1.7_34644.exe?model=ROG%20Ally%20(2023) - - string currentDeviceType = CurrentDevice.GetType().Name; - switch (currentDeviceType) + string currentDeviceType = CurrentDevice.GetType().Name; + switch (currentDeviceType) { - case "AYANEOAIRPlus": - case "ROGAlly": - { - LogManager.LogInformation("Restarting: {0}", CurrentDevice.InternalSensorName); + /* + * workaround for Bosch BMI320/BMI323 (as of 06/20/2023) + * todo: check if still needed with Bosch G-sensor Driver V1.0.1.7 + * https://dlcdnets.asus.com/pub/ASUS/IOTHMD/Image/Driver/Chipset/34644/BoschG-sensor_ROG_Bosch_Z_V1.0.1.7_34644.exe?model=ROG%20Ally%20(2023) - if (CurrentDevice.RestartSensor()) + case "AYANEOAIRPlus": + case "ROGAlly": { - // give the device some breathing space once restarted - Thread.Sleep(500); - - LogManager.LogInformation("Successfully restarted: {0}", CurrentDevice.InternalSensorName); - } - else - LogManager.LogError("Failed to restart: {0}", CurrentDevice.InternalSensorName); - } - break; + LogManager.LogInformation("Restarting: {0}", CurrentDevice.InternalSensorName); - case "SteamDeck": - { - // prevent Steam Deck controller from being hidden by default - if (FirstStart) - SettingsManager.SetProperty("HIDcloakonconnect", false); - } - break; - } - - // initialize splash screen on first start only - if (FirstStart) - { - splashScreen = new SplashScreen(); - splashScreen.Show(); - - SettingsManager.SetProperty("FirstStart", false); - } - - // load window(s) - loadWindows(); - - // load page(s) - loadPages(); - - // manage events - InputsManager.TriggerRaised += InputsManager_TriggerRaised; - PowerManager.SystemStatusChanged += OnSystemStatusChanged; - DeviceManager.UsbDeviceArrived += GenericDeviceUpdated; - DeviceManager.UsbDeviceRemoved += GenericDeviceUpdated; - ControllerManager.ControllerSelected += ControllerManager_ControllerSelected; - VirtualManager.ControllerSelected += VirtualManager_ControllerSelected; - - ToastManager.Start(); - ToastManager.IsEnabled = SettingsManager.GetBoolean("ToastEnable"); + if (CurrentDevice.RestartSensor()) + { + // give the device some breathing space once restarted + Thread.Sleep(500); - // start static managers in sequence + LogManager.LogInformation("Successfully restarted: {0}", CurrentDevice.InternalSensorName); + } + else + LogManager.LogError("Failed to restart: {0}", CurrentDevice.InternalSensorName); + } + break; + */ + + case "SteamDeck": + { + // prevent Steam Deck controller from being hidden by default + if (FirstStart) + SettingsManager.SetProperty("HIDcloakonconnect", false); + } + break; + } + + // initialize splash screen on first start only + SettingsManager.SetProperty("FirstStart", false); + + // initialize UI sounds board + UISounds uiSounds = new UISounds(); + + // load window(s) + SplashScreen.LoadingSequence.Text = "Drawing windows..."; + Dispatcher.Invoke(new Action(() => + { + loadWindows(); + }), DispatcherPriority.Background); // Lower priority + + // load page(s) + SplashScreen.LoadingSequence.Text = "Drawing pages..."; + Dispatcher.Invoke(new Action(() => + { + loadPages(); + }), DispatcherPriority.Background); // Lower priority + + // manage events + InputsManager.TriggerRaised += InputsManager_TriggerRaised; + SystemManager.SystemStatusChanged += OnSystemStatusChanged; + DeviceManager.UsbDeviceArrived += GenericDeviceUpdated; + DeviceManager.UsbDeviceRemoved += GenericDeviceUpdated; + ControllerManager.ControllerSelected += ControllerManager_ControllerSelected; + + ToastManager.Start(); + ToastManager.IsEnabled = SettingsManager.GetBoolean("ToastEnable"); + + // start static managers in sequence + SplashScreen.LoadingSequence.Text = "Initializing managers..."; + GPUManager.Start(); PowerProfileManager.Start(); ProfileManager.Start(); ControllerManager.Start(); @@ -241,600 +239,586 @@ public MainWindow(FileVersionInfo _fileVersionInfo, Assembly CurrentAssembly) DeviceManager.Start(); OSDManager.Start(); LayoutManager.Start(); - PowerManager.Start(); - DynamicLightingManager.Start(); SystemManager.Start(); + DynamicLightingManager.Start(); + MultimediaManager.Start(); VirtualManager.Start(); InputsManager.Start(); SensorsManager.Start(); - TimerManager.Start(); - - // todo: improve overall threading logic - new Thread(() => { PlatformManager.Start(); }).Start(); - new Thread(() => { ProcessManager.Start(); }).Start(); + TimerManager.Start(); + + // todo: improve overall threading logic + new Thread(() => { PlatformManager.Start(); }).Start(); + new Thread(() => { ProcessManager.Start(); }).Start(); new Thread(() => { TaskManager.Start(CurrentExe); }).Start(); - new Thread(() => { PerformanceManager.Start(); }).Start(); - new Thread(() => { UpdateManager.Start(); }).Start(); - - // start setting last - SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; - SettingsManager.Start(); - - // update Position and Size - Height = (int)Math.Max(MinHeight, SettingsManager.GetDouble("MainWindowHeight")); - Width = (int)Math.Max(MinWidth, SettingsManager.GetDouble("MainWindowWidth")); - Left = Math.Min(SystemParameters.PrimaryScreenWidth - MinWidth, SettingsManager.GetDouble("MainWindowLeft")); - Top = Math.Min(SystemParameters.PrimaryScreenHeight - MinHeight, SettingsManager.GetDouble("MainWindowTop")); - navView.IsPaneOpen = SettingsManager.GetBoolean("MainWindowIsPaneOpen"); - } - - private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - // windows shutting down event - if (msg == WM_QUERYENDSESSION) - { - // do something - } - - return IntPtr.Zero; - } - - private void ControllerManager_ControllerSelected(IController Controller) - { - // UI thread (async) - Application.Current.Dispatcher.BeginInvoke(() => - { - GamepadUISelectIcon.Glyph = Controller.GetGlyph(ButtonFlags.B1); - GamepadUISelectIcon.Foreground = Controller.GetGlyphColor(ButtonFlags.B1); - - GamepadUIBackIcon.Glyph = Controller.GetGlyph(ButtonFlags.B2); - GamepadUIBackIcon.Foreground = Controller.GetGlyphColor(ButtonFlags.B2); - - GamepadUIToggleIcon.Glyph = Controller.GetGlyph(ButtonFlags.B4); - GamepadUIToggleIcon.Foreground = Controller.GetGlyphColor(ButtonFlags.B4); - }); - } - - private void GamepadFocusManagerOnFocused(Control control) - { - // UI thread (async) - Application.Current.Dispatcher.BeginInvoke(() => - { - // todo : localize me - string controlType = control.GetType().Name; - switch (controlType) - { - default: - { - GamepadUISelect.Visibility = Visibility.Visible; - GamepadUIBack.Visibility = Visibility.Visible; - GamepadUIToggle.Visibility = Visibility.Collapsed; - - GamepadUISelectDesc.Text = Properties.Resources.MainWindow_Select; - GamepadUIBackDesc.Text = Properties.Resources.MainWindow_Back; - } - break; - - case "Button": - { - GamepadUISelect.Visibility = Visibility.Visible; - GamepadUIBack.Visibility = Visibility.Visible; - - GamepadUISelectDesc.Text = Properties.Resources.MainWindow_Select; - GamepadUIBackDesc.Text = Properties.Resources.MainWindow_Back; - - // To get the first RadioButton in the list, if any - RadioButton firstRadioButton = WPFUtils.FindChildren(control).FirstOrDefault(c => c is RadioButton) as RadioButton; - if (firstRadioButton is not null) - { - GamepadUIToggle.Visibility = Visibility.Visible; - GamepadUIToggleDesc.Text = Properties.Resources.MainWindow_Toggle; - } - } - break; - - case "Slider": - { - GamepadUISelect.Visibility = Visibility.Collapsed; - GamepadUIBack.Visibility = Visibility.Visible; - GamepadUIToggle.Visibility = Visibility.Collapsed; - } - break; - - case "NavigationViewItem": - { - GamepadUISelect.Visibility = Visibility.Visible; - GamepadUIBack.Visibility = Visibility.Collapsed; - GamepadUIToggle.Visibility = Visibility.Collapsed; - - GamepadUISelectDesc.Text = Properties.Resources.MainWindow_Navigate; - } - break; - } - }); - } - - private void AddNotifyIconItem(string name, object tag = null) - { - tag ??= string.Concat(name.Where(c => !char.IsWhiteSpace(c))); - - var menuItemMainWindow = new ToolStripMenuItem(name); - menuItemMainWindow.Tag = tag; - menuItemMainWindow.Click += MenuItem_Click; - notifyIcon.ContextMenuStrip.Items.Add(menuItemMainWindow); - } - - private void AddNotifyIconSeparator() - { - var separator = new ToolStripSeparator(); - notifyIcon.ContextMenuStrip.Items.Add(separator); - } - - private void SettingsManager_SettingValueChanged(string name, object value) - { - switch (name) - { - case "ToastEnable": - ToastManager.IsEnabled = Convert.ToBoolean(value); - break; - case "DesktopProfileOnStart": - if (SettingsManager.IsInitialized) - break; - - var DesktopLayout = Convert.ToBoolean(value); - SettingsManager.SetProperty("DesktopLayoutEnabled", DesktopLayout, false, true); - break; - } - } - - public void SwapWindowState() - { - // UI thread - Application.Current.Dispatcher.Invoke(() => - { - switch (WindowState) - { - case WindowState.Normal: - case WindowState.Maximized: - WindowState = WindowState.Minimized; - break; - case WindowState.Minimized: - WindowState = prevWindowState; - break; - } - }); - } - - public static MainWindow GetCurrent() - { - return CurrentWindow; - } - - private void loadPages() - { - // initialize pages - controllerPage = new ControllerPage("controller"); - controllerPage.Loaded += ControllerPage_Loaded; - - devicePage = new DevicePage("device"); - performancePage = new PerformancePage("performance"); - profilesPage = new ProfilesPage("profiles"); + new Thread(() => { PerformanceManager.Start(); }).Start(); + new Thread(() => { UpdateManager.Start(); }).Start(); + + // start setting last + SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; + SettingsManager.Start(); + + // Load MVVM pages after the Models / data have been created. + overlayquickTools.LoadPages_MVVM(); + LoadPages_MVVM(); + + // update Position and Size + Height = (int)Math.Max(MinHeight, SettingsManager.GetDouble("MainWindowHeight")); + Width = (int)Math.Max(MinWidth, SettingsManager.GetDouble("MainWindowWidth")); + Left = Math.Min(SystemParameters.PrimaryScreenWidth - MinWidth, SettingsManager.GetDouble("MainWindowLeft")); + Top = Math.Min(SystemParameters.PrimaryScreenHeight - MinHeight, SettingsManager.GetDouble("MainWindowTop")); + navView.IsPaneOpen = SettingsManager.GetBoolean("MainWindowIsPaneOpen"); + } + + private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + switch (msg) + { + case WM_DISPLAYCHANGE: + case WM_DEVICECHANGE: + DeviceManager.RefreshDisplayAdapters(); + break; + case WM_QUERYENDSESSION: + break; + } + + return IntPtr.Zero; + } + + private void ControllerManager_ControllerSelected(IController Controller) + { + // UI thread (async) + Application.Current.Dispatcher.Invoke(() => + { + GamepadUISelectIcon.Glyph = Controller.GetGlyph(ButtonFlags.B1); + GamepadUISelectIcon.Foreground = Controller.GetGlyphColor(ButtonFlags.B1); + + GamepadUIBackIcon.Glyph = Controller.GetGlyph(ButtonFlags.B2); + GamepadUIBackIcon.Foreground = Controller.GetGlyphColor(ButtonFlags.B2); + + GamepadUIToggleIcon.Glyph = Controller.GetGlyph(ButtonFlags.B4); + GamepadUIToggleIcon.Foreground = Controller.GetGlyphColor(ButtonFlags.B4); + }); + } + + private void GamepadFocusManagerOnFocused(Control control) + { + // UI thread (async) + Application.Current.Dispatcher.Invoke(() => + { + // todo : localize me + string controlType = control.GetType().Name; + switch (controlType) + { + default: + { + GamepadUISelect.Visibility = Visibility.Visible; + GamepadUIBack.Visibility = Visibility.Visible; + GamepadUIToggle.Visibility = Visibility.Collapsed; + + GamepadUISelectDesc.Text = Properties.Resources.MainWindow_Select; + GamepadUIBackDesc.Text = Properties.Resources.MainWindow_Back; + } + break; + + case "Button": + { + GamepadUISelect.Visibility = Visibility.Visible; + GamepadUIBack.Visibility = Visibility.Visible; + + GamepadUISelectDesc.Text = Properties.Resources.MainWindow_Select; + GamepadUIBackDesc.Text = Properties.Resources.MainWindow_Back; + + // To get the first RadioButton in the list, if any + RadioButton firstRadioButton = WPFUtils.FindChildren(control).FirstOrDefault(c => c is RadioButton) as RadioButton; + if (firstRadioButton is not null) + { + GamepadUIToggle.Visibility = Visibility.Visible; + GamepadUIToggleDesc.Text = Properties.Resources.MainWindow_Toggle; + } + } + break; + + case "Slider": + { + GamepadUISelect.Visibility = Visibility.Collapsed; + GamepadUIBack.Visibility = Visibility.Visible; + GamepadUIToggle.Visibility = Visibility.Collapsed; + } + break; + + case "NavigationViewItem": + { + GamepadUISelect.Visibility = Visibility.Visible; + GamepadUIBack.Visibility = Visibility.Collapsed; + GamepadUIToggle.Visibility = Visibility.Collapsed; + + GamepadUISelectDesc.Text = Properties.Resources.MainWindow_Navigate; + } + break; + } + }); + } + + private void AddNotifyIconItem(string name, object tag = null) + { + tag ??= string.Concat(name.Where(c => !char.IsWhiteSpace(c))); + + var menuItemMainWindow = new ToolStripMenuItem(name); + menuItemMainWindow.Tag = tag; + menuItemMainWindow.Click += MenuItem_Click; + notifyIcon.ContextMenuStrip.Items.Add(menuItemMainWindow); + } + + private void AddNotifyIconSeparator() + { + var separator = new ToolStripSeparator(); + notifyIcon.ContextMenuStrip.Items.Add(separator); + } + + private void SettingsManager_SettingValueChanged(string name, object value) + { + switch (name) + { + case "ToastEnable": + ToastManager.IsEnabled = Convert.ToBoolean(value); + break; + case "DesktopProfileOnStart": + if (SettingsManager.IsInitialized) + break; + + var DesktopLayout = Convert.ToBoolean(value); + SettingsManager.SetProperty("DesktopLayoutEnabled", DesktopLayout, false, true); + break; + } + } + + public void SwapWindowState() + { + // UI thread + Application.Current.Dispatcher.Invoke(() => + { + switch (WindowState) + { + case WindowState.Normal: + case WindowState.Maximized: + WindowState = WindowState.Minimized; + break; + case WindowState.Minimized: + WindowState = prevWindowState; + break; + } + }); + } + + public static MainWindow GetCurrent() + { + return CurrentWindow; + } + + private void loadPages() + { + // initialize pages + controllerPage = new ControllerPage("controller"); + controllerPage.Loaded += ControllerPage_Loaded; + + devicePage = new DevicePage("device"); + profilesPage = new ProfilesPage("profiles"); settingsPage = new SettingsPage("settings"); - aboutPage = new AboutPage("about"); - overlayPage = new OverlayPage("overlay"); + + overlayPage = new OverlayPage("overlay"); hotkeysPage = new HotkeysPage("hotkeys"); - layoutPage = new LayoutPage("layout", navView); - notificationsPage = new NotificationsPage("notifications"); - notificationsPage.StatusChanged += NotificationsPage_LayoutUpdated; - // store pages - _pages.Add("ControllerPage", controllerPage); + notificationsPage = new NotificationsPage("notifications"); + notificationsPage.StatusChanged += NotificationsPage_LayoutUpdated; + + // store pages + _pages.Add("ControllerPage", controllerPage); _pages.Add("DevicePage", devicePage); - _pages.Add("PerformancePage", performancePage); - _pages.Add("ProfilesPage", profilesPage); - _pages.Add("AboutPage", aboutPage); - _pages.Add("OverlayPage", overlayPage); - _pages.Add("SettingsPage", settingsPage); - _pages.Add("HotkeysPage", hotkeysPage); - _pages.Add("LayoutPage", layoutPage); - _pages.Add("NotificationsPage", notificationsPage); - } - - private void loadWindows() - { - // initialize overlay - overlayModel = new OverlayModel(); - overlayTrackpad = new OverlayTrackpad(); - overlayquickTools = new OverlayQuickTools(); - } - - private void GenericDeviceUpdated(PnPDevice device, DeviceEventArgs obj) - { - // todo: improve me - CurrentDevice.PullSensors(); - - aboutPage.UpdateDevice(device); - settingsPage.UpdateDevice(device); - } - - private void InputsManager_TriggerRaised(string listener, InputsChord input, InputsHotkeyType type, bool IsKeyDown, - bool IsKeyUp) - { - switch (listener) - { - case "quickTools": - overlayquickTools.ToggleVisibility(); - break; - case "overlayGamepad": - overlayModel.ToggleVisibility(); - break; - case "overlayTrackpads": - overlayTrackpad.ToggleVisibility(); - break; - case "shortcutMainwindow": - SwapWindowState(); - break; - } - } - - private void MenuItem_Click(object? sender, EventArgs e) - { - switch (((ToolStripMenuItem)sender).Tag) - { - case "MainWindow": - SwapWindowState(); - break; - case "QuickTools": - overlayquickTools.ToggleVisibility(); - break; - case "Exit": - appClosing = true; - Close(); - break; - } - } - - private void Window_Loaded(object sender, RoutedEventArgs e) - { - // load gamepad navigation maanger - gamepadFocusManager = new(this, ContentFrame); - - HwndSource source = PresentationSource.FromVisual(this) as HwndSource; - source.AddHook(WndProc); // Hook into the window's message loop - } - - private void ControllerPage_Loaded(object sender, RoutedEventArgs e) - { - if (IsReady) - return; - - // hide splashscreen - if (splashScreen is not null) - splashScreen.Close(); - - // home page has loaded, display main window - WindowState = SettingsManager.GetBoolean("StartMinimized") - ? WindowState.Minimized - : (WindowState)SettingsManager.GetInt("MainWindowState"); - prevWindowState = (WindowState)SettingsManager.GetInt("MainWindowPrevState"); - - IsReady = true; - } - - private void NotificationsPage_LayoutUpdated(int status) - { - bool hasNotification = Convert.ToBoolean(status); - - // UI thread (async) - Application.Current.Dispatcher.BeginInvoke(() => - { - HasNotifications.Visibility = hasNotification ? Visibility.Visible : Visibility.Collapsed; - }); - } - - private void VirtualManager_ControllerSelected(HIDmode HIDmode) - { - Application.Current.Dispatcher.BeginInvoke(() => - { - overlayModel.UpdateHIDMode(HIDmode); - }); - CurrentDevice.SetKeyPressDelay(HIDmode); - } - - public void UpdateSettings(Dictionary args) - { - foreach (var pair in args) - { - var name = pair.Key; - var property = pair.Value; - - switch (name) - { - case "DSUEnabled": - break; - case "DSUip": - break; - case "DSUport": - break; - } - } - } - - // no code from the cases inside this function will be called on program start - private async void OnSystemStatusChanged(PowerManager.SystemStatus status, PowerManager.SystemStatus prevStatus) - { - if (status == prevStatus) - return; - - switch (status) - { - case PowerManager.SystemStatus.SystemReady: - { - // resume from sleep - if (prevStatus == PowerManager.SystemStatus.SystemPending) - { - // use device-specific delay - await Task.Delay(CurrentDevice.ResumeDelay); - - // restore inputs manager - InputsManager.Start(); - - // start timer manager - TimerManager.Start(); - - // resume the virtual controller last - VirtualManager.Resume(); - - // restart IMU - SensorsManager.Resume(true); - } - - // open device, when ready - new Thread(() => - { - // wait for all HIDs to be ready - while (!CurrentDevice.IsReady()) - Thread.Sleep(500); - - // open current device (threaded to avoid device to hang) - CurrentDevice.Open(); - }).Start(); - } - break; - - case PowerManager.SystemStatus.SystemPending: - // sleep - { - // stop the virtual controller - VirtualManager.Suspend(); - - // stop timer manager - TimerManager.Stop(); - - // stop sensors - SensorsManager.Stop(); - // pause inputs manager - InputsManager.Stop(); - - // close current device - CurrentDevice.Close(); - } - break; - } - } - - #region UI - - private void navView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) - { - if (args.InvokedItemContainer is not null) - { - var navItem = (NavigationViewItem)args.InvokedItemContainer; - var navItemTag = (string)navItem.Tag; - - switch (navItemTag) - { - default: - preNavItemTag = navItemTag; - break; - } - - NavView_Navigate(preNavItemTag); - } - } - - public void NavView_Navigate(string navItemTag) - { - var item = _pages.FirstOrDefault(p => p.Key.Equals(navItemTag)); - var _page = item.Value; - - // Get the page type before navigation so you can prevent duplicate - // entries in the backstack. - var preNavPageType = ContentFrame.CurrentSourcePageType; - - // Only navigate if the selected page isn't currently loaded. - if (!(_page is null) && !Equals(preNavPageType, _page)) NavView_Navigate(_page); - } - - public static void NavView_Navigate(Page _page) - { - CurrentWindow.ContentFrame.Navigate(_page); - CurrentWindow.scrollViewer.ScrollToTop(); - } - - private void navView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) - { - TryGoBack(); - } - - private void Window_Closed(object sender, EventArgs e) - { - CurrentDevice.Close(); - - notifyIcon.Visible = false; - notifyIcon.Dispose(); - - overlayModel.Close(); - overlayTrackpad.Close(); - overlayquickTools.Close(true); - - VirtualManager.Stop(); - SystemManager.Stop(); - MotionManager.Stop(); - SensorsManager.Stop(); - ControllerManager.Stop(); - InputsManager.Stop(); - DeviceManager.Stop(); - PlatformManager.Stop(); - OSDManager.Stop(); - PowerProfileManager.Stop(); - ProfileManager.Stop(); - LayoutManager.Stop(); - PowerManager.Stop(); - ProcessManager.Stop(); - ToastManager.Stop(); - TaskManager.Stop(); - PerformanceManager.Stop(); - UpdateManager.Stop(); - - // closing page(s) - controllerPage.Page_Closed(); - profilesPage.Page_Closed(); - settingsPage.Page_Closed(); - overlayPage.Page_Closed(); - hotkeysPage.Page_Closed(); - layoutPage.Page_Closed(); - notificationsPage.Page_Closed(); - - // force kill application - Environment.Exit(0); - } - - private async void Window_Closing(object sender, CancelEventArgs e) - { - // position and size settings - switch (WindowState) - { - case WindowState.Normal: - SettingsManager.SetProperty("MainWindowLeft", Left); - SettingsManager.SetProperty("MainWindowTop", Top); - SettingsManager.SetProperty("MainWindowWidth", ActualWidth); - SettingsManager.SetProperty("MainWindowHeight", ActualHeight); - break; - case WindowState.Maximized: - SettingsManager.SetProperty("MainWindowLeft", 0); - SettingsManager.SetProperty("MainWindowTop", 0); - SettingsManager.SetProperty("MainWindowWidth", SystemParameters.MaximizedPrimaryScreenWidth); - SettingsManager.SetProperty("MainWindowHeight", SystemParameters.MaximizedPrimaryScreenHeight); - - break; - } - - SettingsManager.SetProperty("MainWindowState", (int)WindowState); - SettingsManager.SetProperty("MainWindowPrevState", (int)prevWindowState); - - SettingsManager.SetProperty("MainWindowIsPaneOpen", navView.IsPaneOpen); - - if (SettingsManager.GetBoolean("CloseMinimises") && !appClosing) - { - e.Cancel = true; - WindowState = WindowState.Minimized; - return; - } - } - - private void Window_StateChanged(object sender, EventArgs e) - { - switch (WindowState) - { - case WindowState.Minimized: - notifyIcon.Visible = true; - ShowInTaskbar = false; - - if (!NotifyInTaskbar) - { - ToastManager.SendToast(Title, "is running in the background"); - NotifyInTaskbar = true; - } - - break; - case WindowState.Normal: - case WindowState.Maximized: - notifyIcon.Visible = false; - ShowInTaskbar = true; - - Activate(); - Topmost = true; // important - Topmost = false; // important - Focus(); - - prevWindowState = WindowState; - break; - } - } - - private void navView_Loaded(object sender, RoutedEventArgs e) - { - // Add handler for ContentFrame navigation. - ContentFrame.Navigated += On_Navigated; - - // NavView doesn't load any page by default, so load home page. - navView.SelectedItem = navView.MenuItems[0]; - - // If navigation occurs on SelectionChanged, this isn't needed. - // Because we use ItemInvoked to navigate, we need to call Navigate - // here to load the home page. - preNavItemTag = "ControllerPage"; - NavView_Navigate(preNavItemTag); - } - - private void GamepadWindow_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) - { - if (!e.NewFocus.GetType().IsSubclassOf(typeof(Control))) - return; - - GamepadFocusManagerOnFocused((Control)e.NewFocus); - } - - private void GamepadWindow_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) - { - // do something - } - - private bool TryGoBack() - { - if (!ContentFrame.CanGoBack) - return false; - - // Don't go back if the nav pane is overlayed. - if (navView.IsPaneOpen && - (navView.DisplayMode == NavigationViewDisplayMode.Compact || - navView.DisplayMode == NavigationViewDisplayMode.Minimal)) - return false; - - ContentFrame.GoBack(); - return true; - } - - private void On_Navigated(object sender, NavigationEventArgs e) - { - navView.IsBackEnabled = ContentFrame.CanGoBack; - - if (ContentFrame.SourcePageType is not null) - { - CurrentPageName = ContentFrame.CurrentSourcePageType.Name; - - var NavViewItem = navView.MenuItems - .OfType() - .Where(n => n.Tag.Equals(CurrentPageName)).FirstOrDefault(); - - if (!(NavViewItem is null)) - navView.SelectedItem = NavViewItem; + _pages.Add("ProfilesPage", profilesPage); - navView.Header = new TextBlock() { Text = (string)((Page)e.Content).Title }; - } - } + _pages.Add("OverlayPage", overlayPage); + _pages.Add("SettingsPage", settingsPage); + _pages.Add("HotkeysPage", hotkeysPage); - #endregion -} + _pages.Add("NotificationsPage", notificationsPage); + } + + private void LoadPages_MVVM() + { + layoutPage = new LayoutPage("layout", navView); + layoutPage.Initialize(); + + performancePage = new PerformancePage(); + aboutPage = new AboutPage(); + + _pages.Add("LayoutPage", layoutPage); + _pages.Add("PerformancePage", performancePage); + _pages.Add("AboutPage", aboutPage); + } + + private void loadWindows() + { + // initialize overlay + overlayModel = new OverlayModel(); + overlayTrackpad = new OverlayTrackpad(); + overlayquickTools = new OverlayQuickTools(); + } + + private void GenericDeviceUpdated(PnPDevice device, DeviceEventArgs obj) + { + // todo: improve me + CurrentDevice.PullSensors(); + } + + private void InputsManager_TriggerRaised(string listener, InputsChord input, InputsHotkeyType type, bool IsKeyDown, + bool IsKeyUp) + { + switch (listener) + { + case "quickTools": + overlayquickTools.ToggleVisibility(); + break; + case "overlayGamepad": + overlayModel.ToggleVisibility(); + break; + case "overlayTrackpads": + overlayTrackpad.ToggleVisibility(); + break; + case "shortcutMainwindow": + SwapWindowState(); + break; + } + } + + private void MenuItem_Click(object? sender, EventArgs e) + { + switch (((ToolStripMenuItem)sender).Tag) + { + case "MainWindow": + SwapWindowState(); + break; + case "QuickTools": + overlayquickTools.ToggleVisibility(); + break; + case "Exit": + appClosing = true; + Close(); + break; + } + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + // load gamepad navigation maanger + gamepadFocusManager = new(this, ContentFrame); + + HwndSource source = PresentationSource.FromVisual(this) as HwndSource; + source.AddHook(WndProc); // Hook into the window's message loop + + // restore window state + WindowState = SettingsManager.GetBoolean("StartMinimized") ? WindowState.Minimized : (WindowState)SettingsManager.GetInt("MainWindowState"); + prevWindowState = (WindowState)SettingsManager.GetInt("MainWindowPrevState"); + } + + private void ControllerPage_Loaded(object sender, RoutedEventArgs e) + { + // hide splashscreen + if (SplashScreen is not null) + SplashScreen.Close(); + + // home page is ready, display main window + this.Visibility = Visibility.Visible; + } + + private void NotificationsPage_LayoutUpdated(int status) + { + bool hasNotification = Convert.ToBoolean(status); + + // UI thread (async) + Application.Current.Dispatcher.Invoke(() => + { + HasNotifications.Visibility = hasNotification ? Visibility.Visible : Visibility.Collapsed; + }); + } + + // no code from the cases inside this function will be called on program start + private async void OnSystemStatusChanged(SystemManager.SystemStatus status, SystemManager.SystemStatus prevStatus) + { + if (status == prevStatus) + return; + + switch (status) + { + case SystemManager.SystemStatus.SystemReady: + { + if (prevStatus == SystemManager.SystemStatus.SystemPending) + { + // when device resumes from sleep + // use device-specific delay + await Task.Delay(CurrentDevice.ResumeDelay); + + // resume manager(s) + InputsManager.Start(); + TimerManager.Start(); + VirtualManager.Resume(); + SensorsManager.Resume(true); + GPUManager.Start(); + + // resume platform(s) + PlatformManager.LibreHardwareMonitor.Start(); + } + + // open device, when ready + new Thread(() => + { + // wait for all HIDs to be ready + while (!CurrentDevice.IsReady()) + Thread.Sleep(500); + + // open current device (threaded to avoid device to hang) + CurrentDevice.Open(); + }).Start(); + } + break; + + case SystemManager.SystemStatus.SystemPending: + { + // when device goes to sleep + // suspend manager(s) + VirtualManager.Suspend(); + TimerManager.Stop(); + SensorsManager.Stop(); + InputsManager.Stop(); + GPUManager.Stop(); + + // suspend platform(s) + PlatformManager.LibreHardwareMonitor.Stop(); + + // close current device + CurrentDevice.Close(); + + // Allow system to sleep + SystemManager.SetThreadExecutionState(SystemManager.ES_CONTINUOUS); + LogManager.LogDebug("Tasks completed. System can now suspend if needed."); + } + break; + } + } + + #region UI + + private void navView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) + { + if (args.InvokedItemContainer is not null) + { + var navItem = (NavigationViewItem)args.InvokedItemContainer; + var navItemTag = (string)navItem.Tag; + + switch (navItemTag) + { + default: + preNavItemTag = navItemTag; + break; + } + + NavView_Navigate(preNavItemTag); + } + } + + public void NavView_Navigate(string navItemTag) + { + var item = _pages.FirstOrDefault(p => p.Key.Equals(navItemTag)); + var _page = item.Value; + + // Get the page type before navigation so you can prevent duplicate + // entries in the backstack. + var preNavPageType = ContentFrame.CurrentSourcePageType; + + // Only navigate if the selected page isn't currently loaded. + if (!(_page is null) && !Equals(preNavPageType, _page)) NavView_Navigate(_page); + } + + public static void NavView_Navigate(Page _page) + { + CurrentWindow.ContentFrame.Navigate(_page); + CurrentWindow.scrollViewer.ScrollToTop(); + } + + private void navView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args) + { + TryGoBack(); + } + + private void Window_Closed(object sender, EventArgs e) + { + CurrentDevice.Close(); + + notifyIcon.Visible = false; + notifyIcon.Dispose(); + + overlayModel.Close(); + overlayTrackpad.Close(); + overlayquickTools.Close(true); + + VirtualManager.Stop(); + MultimediaManager.Stop(); + GPUManager.Stop(); + MotionManager.Stop(); + SensorsManager.Stop(); + ControllerManager.Stop(); + InputsManager.Stop(); + DeviceManager.Stop(); + PlatformManager.Stop(); + OSDManager.Stop(); + PowerProfileManager.Stop(); + ProfileManager.Stop(); + LayoutManager.Stop(); + SystemManager.Stop(); + ProcessManager.Stop(); + ToastManager.Stop(); + TaskManager.Stop(); + PerformanceManager.Stop(); + UpdateManager.Stop(); + + // closing page(s) + controllerPage.Page_Closed(); + profilesPage.Page_Closed(); + settingsPage.Page_Closed(); + overlayPage.Page_Closed(); + hotkeysPage.Page_Closed(); + layoutPage.Page_Closed(); + notificationsPage.Page_Closed(); + + // force kill application + Environment.Exit(0); + } + + private async void Window_Closing(object sender, CancelEventArgs e) + { + // position and size settings + switch (WindowState) + { + case WindowState.Normal: + SettingsManager.SetProperty("MainWindowLeft", Left); + SettingsManager.SetProperty("MainWindowTop", Top); + SettingsManager.SetProperty("MainWindowWidth", ActualWidth); + SettingsManager.SetProperty("MainWindowHeight", ActualHeight); + break; + case WindowState.Maximized: + SettingsManager.SetProperty("MainWindowLeft", 0); + SettingsManager.SetProperty("MainWindowTop", 0); + SettingsManager.SetProperty("MainWindowWidth", SystemParameters.MaximizedPrimaryScreenWidth); + SettingsManager.SetProperty("MainWindowHeight", SystemParameters.MaximizedPrimaryScreenHeight); + + break; + } + + SettingsManager.SetProperty("MainWindowState", (int)WindowState); + SettingsManager.SetProperty("MainWindowPrevState", (int)prevWindowState); + + SettingsManager.SetProperty("MainWindowIsPaneOpen", navView.IsPaneOpen); + + if (SettingsManager.GetBoolean("CloseMinimises") && !appClosing) + { + e.Cancel = true; + WindowState = WindowState.Minimized; + return; + } + } + + private void Window_StateChanged(object sender, EventArgs e) + { + switch (WindowState) + { + case WindowState.Minimized: + notifyIcon.Visible = true; + ShowInTaskbar = false; + + if (!NotifyInTaskbar) + { + ToastManager.SendToast(Title, "is running in the background"); + NotifyInTaskbar = true; + } + + break; + case WindowState.Normal: + case WindowState.Maximized: + notifyIcon.Visible = false; + ShowInTaskbar = true; + + Activate(); + Topmost = true; // important + Topmost = false; // important + Focus(); + + prevWindowState = WindowState; + break; + } + } + + private void navView_Loaded(object sender, RoutedEventArgs e) + { + // Add handler for ContentFrame navigation. + ContentFrame.Navigated += On_Navigated; + + // NavView doesn't load any page by default, so load home page. + navView.SelectedItem = navView.MenuItems[0]; + + // If navigation occurs on SelectionChanged, this isn't needed. + // Because we use ItemInvoked to navigate, we need to call Navigate + // here to load the home page. + preNavItemTag = "ControllerPage"; + NavView_Navigate(preNavItemTag); + } + + private void GamepadWindow_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + if (!e.NewFocus.GetType().IsSubclassOf(typeof(Control))) + return; + + GamepadFocusManagerOnFocused((Control)e.NewFocus); + } + + private void GamepadWindow_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + // do something + } + + private bool TryGoBack() + { + if (!ContentFrame.CanGoBack) + return false; + + // Don't go back if the nav pane is overlayed. + if (navView.IsPaneOpen && + (navView.DisplayMode == NavigationViewDisplayMode.Compact || + navView.DisplayMode == NavigationViewDisplayMode.Minimal)) + return false; + + ContentFrame.GoBack(); + return true; + } + + private void On_Navigated(object sender, NavigationEventArgs e) + { + navView.IsBackEnabled = ContentFrame.CanGoBack; + + if (ContentFrame.SourcePageType is not null) + { + CurrentPageName = ContentFrame.CurrentSourcePageType.Name; + + var NavViewItem = navView.MenuItems + .OfType() + .Where(n => n.Tag.Equals(CurrentPageName)).FirstOrDefault(); + + if (!(NavViewItem is null)) + navView.SelectedItem = NavViewItem; + + navView.Header = new TextBlock() { Text = (string)((Page)e.Content).Title }; + } + } + + #endregion +} diff --git a/README.md b/README.md index db7a2b50b..155b1e7e3 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ A few examples of the most common use cases are: - You want to add universal motion controls (UMC) to any game. - You want to add high-precision motion controls to your Windows game library through [Steam](https://store.steampowered.com/controller/update/dec15). - You want to play your Sony Playstation 4 library through [PlayStation Now](https://www.playstation.com/en-us/ps-now/) or [PS4 Remote Play](). -- You want to enjoy all your [Wii](https://dolphin-emu.org/), [WiiU](https://cemu.info/) and [Switch](https://yuzu-emu.org/) games with full motion controls through UDP motion control protocol. +- You want to enjoy all your [Wii](https://dolphin-emu.org/), [WiiU](https://cemu.info/) and [Switch](https://ryujinx.org/) games with full motion controls through UDP motion control protocol. [Youtube Channel](https://www.youtube.com/channel/UCFLra6QVYJYeaWp2mGaq3Og)