From f0951c505fec92a48f24482b801c99e3556196ea Mon Sep 17 00:00:00 2001 From: Lesueur Benjamin Date: Fri, 12 Jan 2024 11:32:56 +0100 Subject: [PATCH 01/39] Implement CustomWpf (#154) * Disable the RealTimeStylus for WPF Applications * Update MainWindow.xaml.cs * test ? --- HandheldCompanion/HandheldCompanion.csproj | 3 +++ .../Views/Windows/MainWindow.xaml.cs | 27 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/HandheldCompanion/HandheldCompanion.csproj b/HandheldCompanion/HandheldCompanion.csproj index 3b10e4848..1208c149f 100644 --- a/HandheldCompanion/HandheldCompanion.csproj +++ b/HandheldCompanion/HandheldCompanion.csproj @@ -141,6 +141,9 @@ + + + diff --git a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs index f122323f7..a5dd3788d 100644 --- a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs +++ b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs @@ -98,7 +98,32 @@ public MainWindow(FileVersionInfo _fileVersionInfo, Assembly CurrentAssembly) var process = Process.GetCurrentProcess(); // fix touch support - var tablets = Tablet.TabletDevices; + 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 }); + } + } + }*/ // get first start bool FirstStart = SettingsManager.GetBoolean("FirstStart"); From 3bec678655dd5d4651fcff33095cdfa77ea7bc65 Mon Sep 17 00:00:00 2001 From: Lesueur Benjamin Date: Sat, 13 Jan 2024 16:57:52 +0100 Subject: [PATCH 02/39] Restore LegionGo fan control (#159) * restore Legion Go FanControl ability (requires BIOS v29) * Implement Sapientia function getUSBVerify() * Implement WMI class (wip) * implement new functions - GetFanFullSpeedAsync - SetFanFullSpeedAsync - SetFanTable - SetSmartFanMode * implement SetCPUPowerLimit() --- HandheldCompanion/Devices/Lenovo/LegionGo.cs | 178 +++++++++--------- .../Devices/Lenovo/SapientiaUsb.cs | 24 ++- .../ManagementObjectSearcherExtensions.cs | 12 ++ HandheldCompanion/Utils/LambdaDisposable.cs | 17 ++ .../Views/QuickPages/QuickDevicePage.xaml.cs | 2 +- HandheldCompanion/WMI.cs | 126 +++++++++++++ 6 files changed, 263 insertions(+), 96 deletions(-) create mode 100644 HandheldCompanion/Extensions/ManagementObjectSearcherExtensions.cs create mode 100644 HandheldCompanion/Utils/LambdaDisposable.cs create mode 100644 HandheldCompanion/WMI.cs diff --git a/HandheldCompanion/Devices/Lenovo/LegionGo.cs b/HandheldCompanion/Devices/Lenovo/LegionGo.cs index 49076e93f..befd37d41 100644 --- a/HandheldCompanion/Devices/Lenovo/LegionGo.cs +++ b/HandheldCompanion/Devices/Lenovo/LegionGo.cs @@ -29,7 +29,74 @@ public enum LegionMode Custom = 0xFF, } - private FanTable fanTable = new(); + public enum CapabilityID + { + IGPUMode = 0x00010000, + FlipToStart = 0x00030000, + NvidiaGPUDynamicDisplaySwitching = 0x00040000, + AMDSmartShiftMode = 0x00050001, + AMDSkinTemperatureTracking = 0x00050002, + SupportedPowerModes = 0x00070000, + LegionZoneSupportVersion = 0x00090000, + IGPUModeChangeStatus = 0x000F0000, + CPUShortTermPowerLimit = 0x0101FF00, + CPULongTermPowerLimit = 0x0102FF00, + CPUPeakPowerLimit = 0x0103FF00, + CPUTemperatureLimit = 0x0104FF00, + APUsPPTPowerLimit = 0x0105FF00, + CPUCrossLoadingPowerLimit = 0x0106FF00, + CPUPL1Tau = 0x0107FF00, + GPUPowerBoost = 0x0201FF00, + GPUConfigurableTGP = 0x0202FF00, + GPUTemperatureLimit = 0x0203FF00, + GPUTotalProcessingPowerTargetOnAcOffsetFromBaseline = 0x0204FF00, + GPUStatus = 0x02070000, + GPUDidVid = 0x02090000, + InstantBootAc = 0x03010001, + InstantBootUsbPowerDelivery = 0x03010002, + FanFullSpeed = 0x04020000, + CpuCurrentFanSpeed = 0x04030001, + GpuCurrentFanSpeed = 0x04030002, + CpuCurrentTemperature = 0x05040000, + GpuCurrentTemperature = 0x05050000 + } + + private Task GetFanFullSpeedAsync() => + WMI.CallAsync("root\\WMI", + $"SELECT * FROM LENOVO_OTHER_METHOD", + "GetFeatureValue", + new() { { "IDs", (int)CapabilityID.FanFullSpeed } }, + pdc => Convert.ToInt32(pdc["Value"].Value) == 1); + + public Task SetFanFullSpeedAsync(bool enabled) => + WMI.CallAsync("root\\WMI", + $"SELECT * FROM LENOVO_OTHER_METHOD", + "SetFeatureValue", + new() + { + { "IDs", (int)CapabilityID.FanFullSpeed }, + { "value", enabled ? 1 : 0 }, + }); + + private Task SetFanTable(FanTable fanTable) => WMI.CallAsync("root\\WMI", + $"SELECT * FROM LENOVO_FAN_METHOD", + "Fan_Set_Table", + new() { { "FanTable", fanTable.GetBytes() } }); + + private Task SetSmartFanMode(int fanMode) => WMI.CallAsync("root\\WMI", + $"SELECT * FROM LENOVO_GAMEZONE_DATA", + "SetSmartFanMode", + new() { { "Data", fanMode } }); + + private Task SetCPUPowerLimit(CapabilityID capabilityID, int limit) => + WMI.CallAsync("root\\WMI", + $"SELECT * FROM LENOVO_OTHER_METHOD", + "SetFeatureValue", + new() + { + { "IDs", (int)capabilityID }, + { "value", limit }, + }); public const byte INPUT_HID_ID = 0x04; @@ -70,7 +137,7 @@ public LegionGo() // device specific capacities Capabilities |= DeviceCapabilities.None; - // Capabilities |= DeviceCapabilities.FanControl; + Capabilities |= DeviceCapabilities.FanControl; Capabilities |= DeviceCapabilities.DynamicLighting; Capabilities |= DeviceCapabilities.DynamicLightingBrightness; @@ -139,29 +206,16 @@ public LegionGo() DefaultLayout.ButtonLayout[ButtonFlags.B8] = new List() { new MouseActions { MouseType = MouseActionsType.ScrollDown } }; Init(); - } - - public override void SetFanControl(bool enable, int mode = 0) - { - // do something - } - - public void SetFanFullSpeed(bool enabled) - { - // Fan control: Default, Full (0, 1) - ECRAMWrite(0x8A, (byte)(enabled ? 1 : 0)); - } - public override void SetFanDuty(double percent) - { - // do something + Task task = Task.Run(async () => await GetFanFullSpeedAsync()); + bool FanFullSpeed = task.Result; } private void PowerProfileManager_Applied(PowerProfile profile, UpdateSource source) { - if (profile.FanProfile.fanMode == FanMode.Hardware) - fanTable = new(new ushort[] { 44, 48, 55, 60, 71, 79, 87, 87, 100, 100 }); - else + FanTable fanTable = new(new ushort[] { 44, 48, 55, 60, 71, 79, 87, 87, 100, 100 }); + if (profile.FanProfile.fanMode != FanMode.Hardware) + { fanTable = new(new ushort[] { (ushort)profile.FanProfile.fanSpeeds[1], (ushort)profile.FanProfile.fanSpeeds[2], @@ -174,81 +228,19 @@ private void PowerProfileManager_Applied(PowerProfile profile, UpdateSource sour (ushort)profile.FanProfile.fanSpeeds[9], (ushort)profile.FanProfile.fanSpeeds[10], }); + } - try - { - // Fan control - ManagementScope managementScope = new ManagementScope("root\\WMI"); - managementScope.Connect(); - ObjectQuery objectQuery = new ObjectQuery("SELECT * FROM LENOVO_FAN_METHOD"); - using (ManagementObjectCollection searcher = new ManagementObjectSearcher(managementScope, objectQuery).Get()) - { - var obj = searcher.Cast().FirstOrDefault(); - if (obj is ManagementObject mo) - { - using (mo) - { - // Invoke the Fan_Set_Table method - var inParams = mo.GetMethodParameters("Fan_Set_Table"); - inParams["FanTable"] = fanTable.GetBytes(); - mo.InvokeMethod("Fan_Set_Table", inParams, null); - - // Invoke the Fan_Get_Table method - inParams = mo.GetMethodParameters("Fan_Get_Table"); - inParams["FanID"] = 1; - inParams["SensorID"] = 0; - - ManagementBaseObject outParams = mo.InvokeMethod("Fan_Get_Table", inParams, null); - - /* Read output - uint fanTableSize = (uint)outParams["FanTableSize"]; - uint[] fanTableArray = (uint[])outParams["FanTable"]; - uint sensorTableSize = (uint)outParams["SensorTableSize"]; - uint[] sensorTableArray = (uint[])outParams["SensorTable"]; - Debug.WriteLine("fanTable:{0}", string.Join(',', fanTable)); - */ - } - } - } - } catch { } + SetFanTable(fanTable); + SetSmartFanMode(profile.OEMPowerMode); - Task.Run(async () => + /* + if (profile.TDPOverrideEnabled && !profile.AutoTDPEnabled) { - // Power mode - int GetSmartFanMode = -1; - - DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(4)); - while (DateTime.Now < timeout && GetSmartFanMode != profile.OEMPowerMode) - { - try - { - - ManagementScope managementScope = new ManagementScope("root\\WMI"); - managementScope.Connect(); - ObjectQuery objectQuery = new ObjectQuery("SELECT * FROM LENOVO_GAMEZONE_DATA"); - using (ManagementObjectCollection searcher = new ManagementObjectSearcher(managementScope, objectQuery).Get()) - { - var obj = searcher.Cast().FirstOrDefault(); - if (obj is ManagementObject mo) - { - using (mo) - { - // Update value - ManagementBaseObject param = mo.GetMethodParameters("SetSmartFanMode"); - param["Data"] = profile.OEMPowerMode; - mo.InvokeMethod("SetSmartFanMode", param, null); - - // Read output - GetSmartFanMode = Convert.ToInt32(mo.InvokeMethod("GetSmartFanMode", null, null)?.Properties["Data"].Value); - } - } - } - } - catch { } - - await Task.Delay(1000); - } - }); + SetCPUPowerLimit(CapabilityID.CPUShortTermPowerLimit, (int)profile.TDPOverrideValues[0]); + SetCPUPowerLimit(CapabilityID.CPULongTermPowerLimit, (int)profile.TDPOverrideValues[1]); + SetCPUPowerLimit(CapabilityID.CPUPeakPowerLimit, (int)profile.TDPOverrideValues[2]); + } + */ } public override bool Open() @@ -289,7 +281,7 @@ public override void Close() } // Reset the fan speed to default before device shutdown/restart - SetFanFullSpeed(false); + SetFanFullSpeedAsync(false); base.Close(); } diff --git a/HandheldCompanion/Devices/Lenovo/SapientiaUsb.cs b/HandheldCompanion/Devices/Lenovo/SapientiaUsb.cs index c6c797df8..20a6f6810 100644 --- a/HandheldCompanion/Devices/Lenovo/SapientiaUsb.cs +++ b/HandheldCompanion/Devices/Lenovo/SapientiaUsb.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; namespace HandheldCompanion.Devices.Lenovo { @@ -136,9 +137,28 @@ public struct GyroSensorStatus //恢复出厂设置 device: 1:RX,2:Dongle; 3:左手柄 4:右手柄 [DllImport("SapientiaUsb.dll", CallingConvention = CallingConvention.StdCall)] public static extern bool SetDeviceDefault(int device); + //手柄版本信息 + [DllImport("SapientiaUsb.dll", CallingConvention = CallingConvention.StdCall)] + public static extern VERSION getUSBVerify(int device); - //导出类 + [StructLayout(LayoutKind.Sequential)] + public struct VERSION + { + public int verPro; + public int verCMD; + public int verFir; + public int verHard; + public VERSION(int verPro, int verCMD, int verFir, int verHard) + { + this.verPro = verPro; + this.verCMD = verCMD; + this.verFir = verFir; + this.verHard = verHard; + } + } + + //导出类 [StructLayout(LayoutKind.Sequential)] public struct LightionProfile { diff --git a/HandheldCompanion/Extensions/ManagementObjectSearcherExtensions.cs b/HandheldCompanion/Extensions/ManagementObjectSearcherExtensions.cs new file mode 100644 index 000000000..db8b427b8 --- /dev/null +++ b/HandheldCompanion/Extensions/ManagementObjectSearcherExtensions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Linq; +using System.Management; +using System.Threading.Tasks; + +namespace HandheldCompanion.Extensions +{ + public static class ManagementObjectSearcherExtensions + { + public static Task> GetAsync(this ManagementObjectSearcher mos) => Task.Run(() => mos.Get().Cast()); + } +} diff --git a/HandheldCompanion/Utils/LambdaDisposable.cs b/HandheldCompanion/Utils/LambdaDisposable.cs new file mode 100644 index 000000000..3504eb4a7 --- /dev/null +++ b/HandheldCompanion/Utils/LambdaDisposable.cs @@ -0,0 +1,17 @@ +using System; + +namespace HandheldCompanion.Utils +{ + public class LambdaDisposable : IDisposable + { + private readonly Action _action; + + public LambdaDisposable(Action action) => _action = action; + + public void Dispose() + { + GC.SuppressFinalize(this); + _action(); + } + } +} diff --git a/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs index f08808f74..3a766261b 100644 --- a/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs @@ -262,7 +262,7 @@ private void Toggle_cFFanSpeed_Toggled(object sender, RoutedEventArgs e) if (MainWindow.CurrentDevice is LegionGo device) { ToggleSwitch toggleSwitch = (ToggleSwitch)sender; - device.SetFanFullSpeed(toggleSwitch.IsOn); + device.SetFanFullSpeedAsync(toggleSwitch.IsOn); } } diff --git a/HandheldCompanion/WMI.cs b/HandheldCompanion/WMI.cs new file mode 100644 index 000000000..91b4f192b --- /dev/null +++ b/HandheldCompanion/WMI.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management; +using System.Threading.Tasks; +using HandheldCompanion.Extensions; +using HandheldCompanion.Utils; + +namespace HandheldCompanion +{ + public static class WMI + { + public static async Task ExistsAsync(string scope, FormattableString query) + { + try + { + var queryFormatted = query.ToString(WMIPropertyValueFormatter.Instance); + var mos = new ManagementObjectSearcher(scope, queryFormatted); + var managementObjects = await mos.GetAsync().ConfigureAwait(false); + return managementObjects.Any(); + } + catch + { + return false; + } + } + + public static IDisposable Listen(string scope, FormattableString query, Action handler) + { + var queryFormatted = query.ToString(WMIPropertyValueFormatter.Instance); + var watcher = new ManagementEventWatcher(scope, queryFormatted); + watcher.EventArrived += (_, e) => handler(e.NewEvent.Properties); + watcher.Start(); + + return new LambdaDisposable(() => + { + watcher.Stop(); + watcher.Dispose(); + }); + } + + public static async Task> ReadAsync(string scope, FormattableString query, Func converter) + { + try + { + var queryFormatted = query.ToString(WMIPropertyValueFormatter.Instance); + var mos = new ManagementObjectSearcher(scope, queryFormatted); + var managementObjects = await mos.GetAsync().ConfigureAwait(false); + var result = managementObjects.Select(mo => mo.Properties).Select(converter); + return result; + } + catch (ManagementException ex) + { + throw new ManagementException($"Read failed: {ex.Message}. [scope={scope}, query={query}]", ex); + } + } + + public static async Task CallAsync(string scope, FormattableString query, string methodName, Dictionary methodParams) + { + try + { + var queryFormatted = query.ToString(WMIPropertyValueFormatter.Instance); + var mos = new ManagementObjectSearcher(scope, queryFormatted); + var managementObjects = await mos.GetAsync().ConfigureAwait(false); + var managementObject = managementObjects.FirstOrDefault() ?? throw new InvalidOperationException("No results in query"); + + var mo = (ManagementObject)managementObject; + var methodParamsObject = mo.GetMethodParameters(methodName); + foreach (var pair in methodParams) + methodParamsObject[pair.Key] = pair.Value; + + mo.InvokeMethod(methodName, methodParamsObject, new InvokeMethodOptions()); + } + catch (ManagementException ex) + { + throw new ManagementException($"Call failed: {ex.Message}. [scope={scope}, query={query}, methodName={methodName}]", ex); + } + } + + public static async Task CallAsync(string scope, FormattableString query, string methodName, Dictionary methodParams, Func converter) + { + try + { + var queryFormatted = query.ToString(WMIPropertyValueFormatter.Instance); + + var mos = new ManagementObjectSearcher(scope, queryFormatted); + var managementObjects = await mos.GetAsync().ConfigureAwait(false); + var managementObject = managementObjects.FirstOrDefault() ?? throw new InvalidOperationException("No results in query"); + + var mo = (ManagementObject)managementObject; + var methodParamsObject = mo.GetMethodParameters(methodName); + foreach (var pair in methodParams) + methodParamsObject[pair.Key] = pair.Value; + + var resultProperties = mo.InvokeMethod(methodName, methodParamsObject, new InvokeMethodOptions()); + var result = converter(resultProperties.Properties); + return result; + } + catch (ManagementException ex) + { + throw new ManagementException($"Call failed: {ex.Message}. [scope={scope}, query={query}, methodName={methodName}]", ex); + } + } + + public class WMIPropertyValueFormatter : IFormatProvider, ICustomFormatter + { + public static readonly WMIPropertyValueFormatter Instance = new(); + + private WMIPropertyValueFormatter() { } + + public object GetFormat(Type? formatType) + { + if (formatType == typeof(ICustomFormatter)) + return this; + + throw new InvalidOperationException("Invalid type of formatted"); + } + + public string Format(string? format, object? arg, IFormatProvider? formatProvider) + { + var stringArg = arg?.ToString()?.Replace("\\", "\\\\"); + return stringArg ?? string.Empty; + } + } + } +} From bf664f609d4b96c3b00b1e7ae7a4ada27f5ede62 Mon Sep 17 00:00:00 2001 From: Lesueur Benjamin Date: Sat, 13 Jan 2024 18:14:29 +0100 Subject: [PATCH 03/39] Implement support for Intel Graphics Control Library (IGCL) (#158) * Start implementing IGCL (wip) * More work - Implemented GPUManager - Implemented GPU-specific classes (AMDGPU, IntelGPU) - Implemented IGCLBackend (wip) - SystemManager renamed to MultimediaManager - PowerManager renamed to SystemManager * more work on IGCL * prevent crash on null MainThread * prevent useless SetResolution() calls * more work on IGCL * add missing sharpness check * implement ctl_device_adapter_properties_t (wip) * what if the issue was deviceIdx all along... * Update IGCL_Wrapper.dll * fix remaining implementations * implement IntegerScalingType (Intel only) * make sure to use defaultGPU (idx: 0) We need to find a proper way to guess which one is used for 3D rendering I guess or linked to main screen.. * fix ctl_device_adapter_properties_t Marshalling * implemented some form of logic to pick the first available external GPU (if any) * improve GPUManager - add support for Manufacturer: "Advanced Micro Devices, Inc." - improve GPUManager and GPU Start() and Stop() logics - prevent Task Execution within Tasks on AMDGPU * fix a crash when UpdateTimer is null --- .../{Misc => ADLX}/ADLXBackend.cs | 2 +- HandheldCompanion/Controls/ProcessEx.xaml.cs | 8 + .../Devices/AYANEO/AYANEODevice.cs | 2 +- .../GraphicsProcessingUnit/AMDGPU.cs | 188 +++++ .../GraphicsProcessingUnit/GPU.cs | 171 ++++ .../GraphicsProcessingUnit/IntelGPU.cs | 46 + HandheldCompanion/HandheldCompanion.csproj | 3 + HandheldCompanion/IGCL/IGCLBackend.cs | 786 ++++++++++++++++++ HandheldCompanion/IGCL_Wrapper.dll | Bin 0 -> 103424 bytes .../Managers/Desktop/DesktopScreen.cs | 2 +- .../Managers/DynamicLightingManager.cs | 4 +- HandheldCompanion/Managers/GPUManager.cs | 205 +++++ HandheldCompanion/Managers/HotkeysManager.cs | 2 +- HandheldCompanion/Managers/LayoutManager.cs | 2 +- .../Managers/MultimediaManager.cs | 604 ++++++++++++++ .../Managers/PerformanceManager.cs | 162 +--- HandheldCompanion/Managers/PowerManager.cs | 193 ----- HandheldCompanion/Managers/ProcessManager.cs | 3 +- HandheldCompanion/Managers/SettingsManager.cs | 4 +- HandheldCompanion/Managers/SystemManager.cs | 786 +++--------------- HandheldCompanion/Misc/ADLXWrapper.cs | 50 -- HandheldCompanion/Misc/MotherboardInfo.cs | 24 + HandheldCompanion/Misc/Profile.cs | 1 + HandheldCompanion/Platforms/RTSS.cs | 2 +- HandheldCompanion/Processors/Processor.cs | 9 +- .../Properties/Resources.Designer.cs | 38 +- HandheldCompanion/Properties/Resources.resx | 14 +- HandheldCompanion/Utils/ScopedLock.cs | 3 - .../Views/Pages/PerformancePage.xaml.cs | 2 +- .../Views/Pages/ProfilesPage.xaml | 10 +- .../Views/Pages/ProfilesPage.xaml.cs | 55 +- .../Views/Pages/SettingsPage.xaml.cs | 2 +- .../Views/QuickPages/QuickDevicePage.xaml.cs | 22 +- .../Views/QuickPages/QuickHomePage.xaml.cs | 18 +- .../QuickPages/QuickPerformancePage.xaml.cs | 2 +- .../Views/QuickPages/QuickProfilesPage.xaml | 102 ++- .../QuickPages/QuickProfilesPage.xaml.cs | 69 +- .../Views/Windows/MainWindow.xaml.cs | 21 +- .../Views/Windows/OverlayQuickTools.xaml.cs | 8 +- 39 files changed, 2435 insertions(+), 1190 deletions(-) rename HandheldCompanion/{Misc => ADLX}/ADLXBackend.cs (99%) create mode 100644 HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs create mode 100644 HandheldCompanion/GraphicsProcessingUnit/GPU.cs create mode 100644 HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs create mode 100644 HandheldCompanion/IGCL/IGCLBackend.cs create mode 100644 HandheldCompanion/IGCL_Wrapper.dll create mode 100644 HandheldCompanion/Managers/GPUManager.cs create mode 100644 HandheldCompanion/Managers/MultimediaManager.cs delete mode 100644 HandheldCompanion/Managers/PowerManager.cs delete mode 100644 HandheldCompanion/Misc/ADLXWrapper.cs diff --git a/HandheldCompanion/Misc/ADLXBackend.cs b/HandheldCompanion/ADLX/ADLXBackend.cs similarity index 99% rename from HandheldCompanion/Misc/ADLXBackend.cs rename to HandheldCompanion/ADLX/ADLXBackend.cs index d64938e01..a1cfb8672 100644 --- a/HandheldCompanion/Misc/ADLXBackend.cs +++ b/HandheldCompanion/ADLX/ADLXBackend.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace HandheldCompanion.Misc +namespace HandheldCompanion.ADLX { public class ADLXBackend { diff --git a/HandheldCompanion/Controls/ProcessEx.xaml.cs b/HandheldCompanion/Controls/ProcessEx.xaml.cs index f4a5d6b9b..2e47137eb 100644 --- a/HandheldCompanion/Controls/ProcessEx.xaml.cs +++ b/HandheldCompanion/Controls/ProcessEx.xaml.cs @@ -209,6 +209,9 @@ public void Refresh() { using (new ScopedLock(updateLock)) { + if (MainThread is null) + return; + switch (MainThread.ThreadState) { case ThreadState.Wait: @@ -320,4 +323,9 @@ private void HighDPIAware_Toggled(object sender, RoutedEventArgs e) HighDPIAware = !T_HighDPIAware.IsOn; } + + internal void MainThreadDisposed() + { + MainThread = ProcessManager.GetMainThread(Process); + } } \ No newline at end of file diff --git a/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs b/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs index 63c6b2032..86ff9e920 100644 --- a/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs +++ b/HandheldCompanion/Devices/AYANEO/AYANEODevice.cs @@ -25,7 +25,7 @@ public AYANEODevice() { prevPowerStatus = SystemInformation.PowerStatus.PowerLineStatus; prevBatteryLevelPercentage = (int)(SystemInformation.PowerStatus.BatteryLifePercent * 100); - PowerManager.PowerStatusChanged += PowerManager_PowerStatusChanged; + SystemManager.PowerStatusChanged += PowerManager_PowerStatusChanged; } private void PowerManager_PowerStatusChanged(PowerStatus powerStatus) diff --git a/HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs b/HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs new file mode 100644 index 000000000..1796a3a72 --- /dev/null +++ b/HandheldCompanion/GraphicsProcessingUnit/AMDGPU.cs @@ -0,0 +1,188 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Timers; +using HandheldCompanion.ADLX; +using HandheldCompanion.IGCL; +using HandheldCompanion.Utils; +using Windows.ApplicationModel.Store; +using Timer = System.Timers.Timer; + +namespace HandheldCompanion.GraphicsProcessingUnit +{ + public class AMDGPU : GPU + { + #region events + public event RSRStateChangedEventHandler RSRStateChanged; + public delegate void RSRStateChangedEventHandler(bool Supported, bool Enabled, int Sharpness); + #endregion + + private bool prevRSRSupport = false; + private bool prevRSR = false; + private int prevRSRSharpness = -1; + + public bool HasRSRSupport() => Execute(ADLXBackend.HasRSRSupport, false); + public override bool HasIntegerScalingSupport() => Execute(() => ADLXBackend.HasIntegerScalingSupport(0), false); + public override bool HasGPUScalingSupport() => Execute(() => ADLXBackend.HasGPUScalingSupport(0), false); + public override bool HasScalingModeSupport() => Execute(() => ADLXBackend.HasScalingModeSupport(0), false); + + public bool GetRSR() => Execute(ADLXBackend.GetRSR, false); + public int GetRSRSharpness() => Execute(ADLXBackend.GetRSRSharpness, -1); + public override bool GetImageSharpening() => Execute(() => ADLXBackend.GetImageSharpening(0), false); + public int GetImageSharpeningSharpness() => Execute(() => ADLXBackend.GetImageSharpeningSharpness(0), -1); + public override bool GetIntegerScaling() => Execute(() => ADLXBackend.GetIntegerScaling(0), false); + public override bool GetGPUScaling() => Execute(() => ADLXBackend.GetGPUScaling(0), false); + public int GetScalingMode() => Execute(() => ADLXBackend.GetScalingMode(0), -1); + + public bool SetRSRSharpness(int sharpness) => Execute(() => ADLXBackend.SetRSRSharpness(sharpness), false); + public override bool SetImageSharpening(bool enable) => Execute(() => ADLXBackend.SetImageSharpening(0, enable), false); + public bool SetRSR(bool enable) => Execute(() => + { + // mutually exclusive + if (enable) + { + if (ADLXBackend.GetIntegerScaling(0)) + ADLXBackend.SetIntegerScaling(0, false); + + if (ADLXBackend.GetImageSharpening(0)) + ADLXBackend.SetImageSharpening(0, false); + } + + return ADLXBackend.SetRSR(enable); + }, false); + public override bool SetImageSharpeningSharpness(int sharpness) => Execute(() => ADLXBackend.SetImageSharpeningSharpness(0, sharpness), false); + public override bool SetIntegerScaling(bool enabled, byte type = 0) => Execute(() => + { + // mutually exclusive + if (enabled) + { + if (ADLXBackend.GetRSR()) + ADLXBackend.SetRSR(false); + } + + return ADLXBackend.SetIntegerScaling(0, enabled); + }, false); + public override bool SetGPUScaling(bool enabled) => Execute(() => ADLXBackend.SetGPUScaling(0, enabled), false); + public override bool SetScalingMode(int mode) => Execute(() => ADLXBackend.SetScalingMode(0, mode), false); + + public AMDGPU() + { + UpdateTimer = new Timer(UpdateInterval); + UpdateTimer.AutoReset = true; + UpdateTimer.Elapsed += UpdateTimer_Elapsed; + } + + public override void Start() + { + base.Start(); + } + + public override void Stop() + { + base.Stop(); + } + + private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs e) + { + using (new ScopedLock(updateLock)) + { + bool GPUScaling = false; + + try + { + // check for GPU Scaling support + // if yes, get GPU Scaling (bool) + bool GPUScalingSupport = HasGPUScalingSupport(); + if (GPUScalingSupport) + GPUScaling = GetGPUScaling(); + + // check for Scaling Mode support + // if yes, get Scaling Mode (int) + bool ScalingSupport = HasScalingModeSupport(); + int ScalingMode = 0; + if (ScalingSupport) + ScalingMode = GetScalingMode(); + + if (GPUScalingSupport != prevGPUScalingSupport || GPUScaling != prevGPUScaling || ScalingMode != prevScalingMode) + { + // raise event + base.OnGPUScalingChanged(GPUScalingSupport, GPUScaling, ScalingMode); + + prevGPUScaling = GPUScaling; + prevScalingMode = ScalingMode; + prevGPUScalingSupport = GPUScalingSupport; + } + } + catch { } + + try + { + // get rsr + bool RSRSupport = false; + bool RSR = false; + int RSRSharpness = GetRSRSharpness(); + + DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); + while (DateTime.Now < timeout && !RSRSupport) + { + RSRSupport = HasRSRSupport(); + await Task.Delay(250); + } + RSR = GetRSR(); + + if (RSRSupport != prevRSRSupport || RSR != prevRSR || RSRSharpness != prevRSRSharpness) + { + // raise event + RSRStateChanged?.Invoke(RSRSupport, RSR, RSRSharpness); + + prevRSRSupport = RSRSupport; + prevRSR = RSR; + prevRSRSharpness = RSRSharpness; + } + } + catch { } + + try + { + // get gpu scaling and scaling mode + bool IntegerScalingSupport = false; + bool IntegerScaling = false; + + DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); + while (DateTime.Now < timeout && !IntegerScalingSupport) + { + IntegerScalingSupport = HasIntegerScalingSupport(); + await Task.Delay(250); + } + IntegerScaling = GetIntegerScaling(); + + if (IntegerScalingSupport != prevIntegerScalingSupport || IntegerScaling != prevIntegerScaling) + { + // raise event + base.OnIntegerScalingChanged(IntegerScalingSupport, IntegerScaling); + + prevIntegerScalingSupport = IntegerScalingSupport; + prevIntegerScaling = IntegerScaling; + } + } + catch { } + + try + { + bool ImageSharpening = GetImageSharpening(); + int ImageSharpeningSharpness = GetImageSharpeningSharpness(); + + if (ImageSharpening != prevImageSharpening || ImageSharpeningSharpness != prevImageSharpeningSharpness) + { + // raise event + base.OnImageSharpeningChanged(ImageSharpening, ImageSharpeningSharpness); + + prevImageSharpening = ImageSharpening; + prevImageSharpeningSharpness = ImageSharpeningSharpness; + } + } + catch { } + } + } + } +} diff --git a/HandheldCompanion/GraphicsProcessingUnit/GPU.cs b/HandheldCompanion/GraphicsProcessingUnit/GPU.cs new file mode 100644 index 000000000..ab357ca1d --- /dev/null +++ b/HandheldCompanion/GraphicsProcessingUnit/GPU.cs @@ -0,0 +1,171 @@ +using HandheldCompanion.Managers; +using HandheldCompanion.Utils; +using System; +using System.Threading.Tasks; +using System.Timers; + +namespace HandheldCompanion.GraphicsProcessingUnit +{ + public class GPU + { + #region + public event IntegerScalingChangedEvent IntegerScalingChanged; + public delegate void IntegerScalingChangedEvent(bool Supported, bool Enabled); + + public event ImageSharpeningChangedEvent ImageSharpeningChanged; + public delegate void ImageSharpeningChangedEvent(bool Enabled, int Sharpness); + + public event GPUScalingChangedEvent GPUScalingChanged; + public delegate void GPUScalingChangedEvent(bool Supported, bool Enabled, int Mode); + #endregion + + private static GPU gpu; + private static string Manufacturer; + + protected const int UpdateInterval = 2000; + protected Timer UpdateTimer; + + protected bool prevGPUScalingSupport = false; + protected bool prevGPUScaling = false; + protected int prevScalingMode = -1; + + protected bool prevIntegerScalingSupport = false; + protected bool prevIntegerScaling = false; + + protected bool prevImageSharpeningSupport = false; + protected bool prevImageSharpening = false; + protected int prevImageSharpeningSharpness = -1; + + protected LockObject updateLock = new(); + + protected static object wrapperLock = new(); + protected static T Execute(Func func, T defaultValue) + { + lock (wrapperLock) + { + Task task = Task.Run(func); + if (task.Wait(TimeSpan.FromSeconds(5))) + { + return task.Result; + } + + return defaultValue; + } + } + + public GPU() + { + Manufacturer = MotherboardInfo.VideoController; + } + + public static GPU GetCurrent() + { + if (gpu is not null) + return gpu; + + switch (Manufacturer) + { + case "Advanced Micro Devices, Inc.": + gpu = new AMDGPU(); + break; + case "Intel Corporation": + gpu = new IntelGPU(); + break; + } + + return gpu; + } + + public virtual void Start() + { + if (UpdateTimer != null) + UpdateTimer.Start(); + } + + public virtual void Stop() + { + if (UpdateTimer != null) + UpdateTimer.Stop(); + } + + protected virtual void OnIntegerScalingChanged(bool supported, bool enabled) + { + IntegerScalingChanged?.Invoke(supported, enabled); + } + + protected virtual void OnImageSharpeningChanged(bool enabled, int sharpness) + { + ImageSharpeningChanged?.Invoke(enabled, sharpness); + } + + protected virtual void OnGPUScalingChanged(bool supported, bool enabled, int mode) + { + GPUScalingChanged?.Invoke(supported, enabled, mode); + } + + public virtual bool SetImageSharpening(bool enabled) + { + return false; + } + + public virtual bool SetImageSharpeningSharpness(int sharpness) + { + return false; + } + + public virtual bool SetIntegerScaling(bool enabled, byte type) + { + return false; + } + + public virtual bool SetGPUScaling(bool enabled) + { + return false; + } + + public virtual bool SetScalingMode(int scalingMode) + { + return false; + } + + public virtual bool GetGPUScaling() + { + return false; + } + + public virtual bool GetIntegerScaling() + { + return false; + } + + public virtual bool GetImageSharpening() + { + return false; + } + + public virtual bool HasScalingModeSupport() + { + return false; + } + + public virtual bool HasIntegerScalingSupport() + { + return false; + } + + public virtual bool HasGPUScalingSupport() + { + return false; + } + + internal virtual int GetScalingMode() + { + return 0; + } + + internal virtual int GetImageSharpeningSharpness() + { + return 0; + } + } +} diff --git a/HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs b/HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs new file mode 100644 index 000000000..ae974e2ea --- /dev/null +++ b/HandheldCompanion/GraphicsProcessingUnit/IntelGPU.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using System.Timers; +using HandheldCompanion.ADLX; +using HandheldCompanion.IGCL; +using Timer = System.Timers.Timer; + +namespace HandheldCompanion.GraphicsProcessingUnit +{ + public class IntelGPU : GPU + { + #region events + #endregion + + public override bool HasIntegerScalingSupport() => Execute(() => IGCLBackend.HasIntegerScalingSupport(IGCLBackend.deviceIdx, 0), false); + public override bool HasGPUScalingSupport() => Execute(() => IGCLBackend.HasGPUScalingSupport(IGCLBackend.deviceIdx, 0), false); + public override bool HasScalingModeSupport() => Execute(() => IGCLBackend.HasGPUScalingSupport(IGCLBackend.deviceIdx, 0), false); + + public override bool GetGPUScaling() => Execute(() => IGCLBackend.GetGPUScaling(IGCLBackend.deviceIdx, 0), false); + public override bool GetImageSharpening() => Execute(() => IGCLBackend.GetImageSharpening(IGCLBackend.deviceIdx, 0), false); + public int GetImageSharpeningSharpness() => Execute(() => IGCLBackend.GetImageSharpeningSharpness(IGCLBackend.deviceIdx, 0), 0); + public override bool GetIntegerScaling() => Execute(() => IGCLBackend.GetIntegerScaling(IGCLBackend.deviceIdx), false); + + // GPUScaling can't be disabled on Intel GPU ? + public override bool SetGPUScaling(bool enabled) => Execute(() => IGCLBackend.SetGPUScaling(IGCLBackend.deviceIdx, 0), false); + public override bool SetImageSharpening(bool enable) => Execute(() => IGCLBackend.SetImageSharpening(IGCLBackend.deviceIdx, 0, enable), false); + public override bool SetImageSharpeningSharpness(int sharpness) => Execute(() => IGCLBackend.SetImageSharpeningSharpness(IGCLBackend.deviceIdx, 0, sharpness), false); + public override bool SetScalingMode(int mode) => Execute(() => IGCLBackend.SetScalingMode(IGCLBackend.deviceIdx, 0, mode), false); + public override bool SetIntegerScaling(bool enabled, byte type) => Execute(() => IGCLBackend.SetIntegerScaling(IGCLBackend.deviceIdx, enabled, type), false); + + public IntelGPU() + { + } + + public override void Start() + { + IGCLBackend.Initialize(); + base.Start(); + } + + public override void Stop() + { + base.Stop(); + } + } +} diff --git a/HandheldCompanion/HandheldCompanion.csproj b/HandheldCompanion/HandheldCompanion.csproj index 1208c149f..a498ebe41 100644 --- a/HandheldCompanion/HandheldCompanion.csproj +++ b/HandheldCompanion/HandheldCompanion.csproj @@ -229,6 +229,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/HandheldCompanion/IGCL/IGCLBackend.cs b/HandheldCompanion/IGCL/IGCLBackend.cs new file mode 100644 index 000000000..d2c58c089 --- /dev/null +++ b/HandheldCompanion/IGCL/IGCLBackend.cs @@ -0,0 +1,786 @@ +using SharpDX; +using SharpDX.Direct3D9; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows.Documents; +using static HandheldCompanion.IGCL.IGCLBackend; + +namespace HandheldCompanion.IGCL +{ + // Define the wrapper class for IGCL + public class IGCLBackend + { + // Define the types used by the C++ functions + [StructLayout(LayoutKind.Sequential)] + struct ctl_init_args_t + { + public int AppVersion; + public int flags; + public int Size; + public int Version; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_device_adapter_handle_t + { + public IntPtr handle; + } + + public enum ctl_result_t + { + CTL_RESULT_SUCCESS = 0x00000000, ///< success + CTL_RESULT_SUCCESS_STILL_OPEN_BY_ANOTHER_CALLER = 0x00000001, ///< success but still open by another caller + CTL_RESULT_ERROR_SUCCESS_END = 0x0000FFFF, ///< "Success group error code end value, not to be used + ///< " + CTL_RESULT_ERROR_GENERIC_START = 0x40000000, ///< Generic error code starting value, not to be used + CTL_RESULT_ERROR_NOT_INITIALIZED = 0x40000001, ///< Result not initialized + CTL_RESULT_ERROR_ALREADY_INITIALIZED = 0x40000002, ///< Already initialized + CTL_RESULT_ERROR_DEVICE_LOST = 0x40000003, ///< Device hung, reset, was removed, or driver update occurred + CTL_RESULT_ERROR_OUT_OF_HOST_MEMORY = 0x40000004, ///< Insufficient host memory to satisfy call + CTL_RESULT_ERROR_OUT_OF_DEVICE_MEMORY = 0x40000005, ///< Insufficient device memory to satisfy call + CTL_RESULT_ERROR_INSUFFICIENT_PERMISSIONS = 0x40000006, ///< Access denied due to permission level + CTL_RESULT_ERROR_NOT_AVAILABLE = 0x40000007, ///< Resource was removed + CTL_RESULT_ERROR_UNINITIALIZED = 0x40000008, ///< Library not initialized + CTL_RESULT_ERROR_UNSUPPORTED_VERSION = 0x40000009, ///< Generic error code for unsupported versions + CTL_RESULT_ERROR_UNSUPPORTED_FEATURE = 0x4000000a, ///< Generic error code for unsupported features + CTL_RESULT_ERROR_INVALID_ARGUMENT = 0x4000000b, ///< Generic error code for invalid arguments + CTL_RESULT_ERROR_INVALID_API_HANDLE = 0x4000000c, ///< API handle in invalid + CTL_RESULT_ERROR_INVALID_NULL_HANDLE = 0x4000000d, ///< Handle argument is not valid + CTL_RESULT_ERROR_INVALID_NULL_POINTER = 0x4000000e, ///< Pointer argument may not be nullptr + CTL_RESULT_ERROR_INVALID_SIZE = 0x4000000f, ///< Size argument is invalid (e.g., must not be zero) + CTL_RESULT_ERROR_UNSUPPORTED_SIZE = 0x40000010, ///< Size argument is not supported by the device (e.g., too large) + CTL_RESULT_ERROR_UNSUPPORTED_IMAGE_FORMAT = 0x40000011, ///< Image format is not supported by the device + CTL_RESULT_ERROR_DATA_READ = 0x40000012, ///< Data read error + CTL_RESULT_ERROR_DATA_WRITE = 0x40000013, ///< Data write error + CTL_RESULT_ERROR_DATA_NOT_FOUND = 0x40000014, ///< Data not found error + CTL_RESULT_ERROR_NOT_IMPLEMENTED = 0x40000015, ///< Function not implemented + CTL_RESULT_ERROR_OS_CALL = 0x40000016, ///< Operating system call failure + CTL_RESULT_ERROR_KMD_CALL = 0x40000017, ///< Kernel mode driver call failure + CTL_RESULT_ERROR_UNLOAD = 0x40000018, ///< Library unload failure + CTL_RESULT_ERROR_ZE_LOADER = 0x40000019, ///< Level0 loader not found + CTL_RESULT_ERROR_INVALID_OPERATION_TYPE = 0x4000001a, ///< Invalid operation type + CTL_RESULT_ERROR_NULL_OS_INTERFACE = 0x4000001b,///< Null OS interface + CTL_RESULT_ERROR_NULL_OS_ADAPATER_HANDLE = 0x4000001c, ///< Null OS adapter handle + CTL_RESULT_ERROR_NULL_OS_DISPLAY_OUTPUT_HANDLE = 0x4000001d,///< Null display output handle + CTL_RESULT_ERROR_WAIT_TIMEOUT = 0x4000001e, ///< Timeout in Wait function + CTL_RESULT_ERROR_PERSISTANCE_NOT_SUPPORTED = 0x4000001f,///< Persistance not supported + CTL_RESULT_ERROR_PLATFORM_NOT_SUPPORTED = 0x40000020, ///< Platform not supported + CTL_RESULT_ERROR_UNKNOWN_APPLICATION_UID = 0x40000021, ///< Unknown Appplicaion UID in Initialization call + CTL_RESULT_ERROR_INVALID_ENUMERATION = 0x40000022, ///< The enum is not valid + CTL_RESULT_ERROR_FILE_DELETE = 0x40000023, ///< Error in file delete operation + CTL_RESULT_ERROR_RESET_DEVICE_REQUIRED = 0x40000024,///< The device requires a reset. + CTL_RESULT_ERROR_FULL_REBOOT_REQUIRED = 0x40000025, ///< The device requires a full reboot. + CTL_RESULT_ERROR_LOAD = 0x40000026, ///< Library load failure + CTL_RESULT_ERROR_UNKNOWN = 0x4000FFFF, ///< Unknown or internal error + CTL_RESULT_ERROR_RETRY_OPERATION = 0x40010000, ///< Operation failed, retry previous operation again + CTL_RESULT_ERROR_GENERIC_END = 0x4000FFFF, ///< "Generic error code end value, not to be used + ///< " + CTL_RESULT_ERROR_CORE_START = 0x44000000, ///< Core error code starting value, not to be used + CTL_RESULT_ERROR_CORE_OVERCLOCK_NOT_SUPPORTED = 0x44000001, ///< The Overclock is not supported. + CTL_RESULT_ERROR_CORE_OVERCLOCK_VOLTAGE_OUTSIDE_RANGE = 0x44000002, ///< The Voltage exceeds the acceptable min/max. + CTL_RESULT_ERROR_CORE_OVERCLOCK_FREQUENCY_OUTSIDE_RANGE = 0x44000003, ///< The Frequency exceeds the acceptable min/max. + CTL_RESULT_ERROR_CORE_OVERCLOCK_POWER_OUTSIDE_RANGE = 0x44000004, ///< The Power exceeds the acceptable min/max. + CTL_RESULT_ERROR_CORE_OVERCLOCK_TEMPERATURE_OUTSIDE_RANGE = 0x44000005, ///< The Power exceeds the acceptable min/max. + CTL_RESULT_ERROR_CORE_OVERCLOCK_IN_VOLTAGE_LOCKED_MODE = 0x44000006,///< The Overclock is in voltage locked mode. + CTL_RESULT_ERROR_CORE_OVERCLOCK_RESET_REQUIRED = 0x44000007,///< It indicates that the requested change will not be applied until the + ///< device is reset. + CTL_RESULT_ERROR_CORE_OVERCLOCK_WAIVER_NOT_SET = 0x44000008,///< The $OverclockWaiverSet function has not been called. + CTL_RESULT_ERROR_CORE_END = 0x0440FFFF, ///< "Core error code end value, not to be used + ///< " + CTL_RESULT_ERROR_3D_START = 0x60000000, ///< 3D error code starting value, not to be used + CTL_RESULT_ERROR_3D_END = 0x6000FFFF, ///< "3D error code end value, not to be used + ///< " + CTL_RESULT_ERROR_MEDIA_START = 0x50000000, ///< Media error code starting value, not to be used + CTL_RESULT_ERROR_MEDIA_END = 0x5000FFFF, ///< "Media error code end value, not to be used + ///< " + CTL_RESULT_ERROR_DISPLAY_START = 0x48000000, ///< Display error code starting value, not to be used + CTL_RESULT_ERROR_INVALID_AUX_ACCESS_FLAG = 0x48000001, ///< Invalid flag for Aux access + CTL_RESULT_ERROR_INVALID_SHARPNESS_FILTER_FLAG = 0x48000002,///< Invalid flag for Sharpness + CTL_RESULT_ERROR_DISPLAY_NOT_ATTACHED = 0x48000003, ///< Error for Display not attached + CTL_RESULT_ERROR_DISPLAY_NOT_ACTIVE = 0x48000004, ///< Error for display attached but not active + CTL_RESULT_ERROR_INVALID_POWERFEATURE_OPTIMIZATION_FLAG = 0x48000005, ///< Error for invalid power optimization flag + CTL_RESULT_ERROR_INVALID_POWERSOURCE_TYPE_FOR_DPST = 0x48000006,///< DPST is supported only in DC Mode + CTL_RESULT_ERROR_INVALID_PIXTX_GET_CONFIG_QUERY_TYPE = 0x48000007, ///< Invalid query type for pixel transformation get configuration + CTL_RESULT_ERROR_INVALID_PIXTX_SET_CONFIG_OPERATION_TYPE = 0x48000008, ///< Invalid operation type for pixel transformation set configuration + CTL_RESULT_ERROR_INVALID_SET_CONFIG_NUMBER_OF_SAMPLES = 0x48000009, ///< Invalid number of samples for pixel transformation set configuration + CTL_RESULT_ERROR_INVALID_PIXTX_BLOCK_ID = 0x4800000a, ///< Invalid block id for pixel transformation + CTL_RESULT_ERROR_INVALID_PIXTX_BLOCK_TYPE = 0x4800000b, ///< Invalid block type for pixel transformation + CTL_RESULT_ERROR_INVALID_PIXTX_BLOCK_NUMBER = 0x4800000c, ///< Invalid block number for pixel transformation + CTL_RESULT_ERROR_INSUFFICIENT_PIXTX_BLOCK_CONFIG_MEMORY = 0x4800000d, ///< Insufficient memery allocated for BlockConfigs + CTL_RESULT_ERROR_3DLUT_INVALID_PIPE = 0x4800000e, ///< Invalid pipe for 3dlut + CTL_RESULT_ERROR_3DLUT_INVALID_DATA = 0x4800000f, ///< Invalid 3dlut data + CTL_RESULT_ERROR_3DLUT_NOT_SUPPORTED_IN_HDR = 0x48000010, ///< 3dlut not supported in HDR + CTL_RESULT_ERROR_3DLUT_INVALID_OPERATION = 0x48000011, ///< Invalid 3dlut operation + CTL_RESULT_ERROR_3DLUT_UNSUCCESSFUL = 0x48000012, ///< 3dlut call unsuccessful + CTL_RESULT_ERROR_AUX_DEFER = 0x48000013, ///< AUX defer failure + CTL_RESULT_ERROR_AUX_TIMEOUT = 0x48000014, ///< AUX timeout failure + CTL_RESULT_ERROR_AUX_INCOMPLETE_WRITE = 0x48000015, ///< AUX incomplete write failure + CTL_RESULT_ERROR_I2C_AUX_STATUS_UNKNOWN = 0x48000016, ///< I2C/AUX unkonown failure + CTL_RESULT_ERROR_I2C_AUX_UNSUCCESSFUL = 0x48000017, ///< I2C/AUX unsuccessful + CTL_RESULT_ERROR_LACE_INVALID_DATA_ARGUMENT_PASSED = 0x48000018,///< Lace Incorrrect AggressivePercent data or LuxVsAggressive Map data + ///< passed by user + CTL_RESULT_ERROR_EXTERNAL_DISPLAY_ATTACHED = 0x48000019,///< External Display is Attached hence fail the Display Switch + CTL_RESULT_ERROR_CUSTOM_MODE_STANDARD_CUSTOM_MODE_EXISTS = 0x4800001a, ///< Standard custom mode exists + CTL_RESULT_ERROR_CUSTOM_MODE_NON_CUSTOM_MATCHING_MODE_EXISTS = 0x4800001b, ///< Non custom matching mode exists + CTL_RESULT_ERROR_CUSTOM_MODE_INSUFFICIENT_MEMORY = 0x4800001c, ///< Custom mode insufficent memory + CTL_RESULT_ERROR_ADAPTER_ALREADY_LINKED = 0x4800001d, ///< Adapter is already linked + CTL_RESULT_ERROR_ADAPTER_NOT_IDENTICAL = 0x4800001e,///< Adapter is not identical for linking + CTL_RESULT_ERROR_ADAPTER_NOT_SUPPORTED_ON_LDA_SECONDARY = 0x4800001f, ///< Adapter is LDA Secondary, so not supporting requested operation + CTL_RESULT_ERROR_SET_FBC_FEATURE_NOT_SUPPORTED = 0x48000020,///< Set FBC Feature not supported + CTL_RESULT_ERROR_DISPLAY_END = 0x4800FFFF, ///< "Display error code end value, not to be used + ///< " + CTL_RESULT_MAX + } + + // Define a handle type for the ctl_api_handle_t + [StructLayout(LayoutKind.Sequential)] + public struct ctl_api_handle_t + { + public IntPtr handle; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_retro_scaling_caps_t + { + public uint Size; + public byte Version; + public ctl_retro_scaling_type_flags_t SupportedRetroScaling; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_retro_scaling_settings_t + { + public uint Size; + public byte Version; + [MarshalAs(UnmanagedType.U1)] + public bool Get; + [MarshalAs(UnmanagedType.U1)] + public bool Enable; + public ctl_retro_scaling_type_flags_t RetroScalingType; + } + + public enum ctl_retro_scaling_type_flags_t : uint + { + CTL_RETRO_SCALING_TYPE_FLAG_INTEGER = 1, + CTL_RETRO_SCALING_TYPE_FLAG_NEAREST_NEIGHBOUR = 2, + CTL_RETRO_SCALING_TYPE_FLAG_MAX = 0x80000000 + } + + // Define the scaling type flags as an enum + public enum ctl_scaling_type_flag_t : uint + { + CTL_SCALING_TYPE_FLAG_IDENTITY = 1, // No scaling is applied and display manages scaling itself when possible + CTL_SCALING_TYPE_FLAG_CENTERED = 2, // Source is not scaled but place in the center of the target display + CTL_SCALING_TYPE_FLAG_STRETCHED = 4, // Source is stretched to fit the target size + CTL_SCALING_TYPE_FLAG_ASPECT_RATIO_CENTERED_MAX = 8, // The aspect ratio is maintained with the source centered + CTL_SCALING_TYPE_FLAG_CUSTOM = 16, // None of the standard types match this .Additional parameters are required which should be set via a private driver interface + CTL_SCALING_TYPE_FLAG_MAX = 0x80000000 + } + + // Define the scaling caps struct + [StructLayout(LayoutKind.Sequential)] + public struct ctl_scaling_caps_t + { + public uint Size; // [in] size of this structure + public byte Version; // [in] version of this structure + public ctl_scaling_type_flag_t SupportedScaling; // [out] Supported scaling types. Refer ::ctl_scaling_type_flag_t + } + + // Define the delegate type for the done function pointer + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void doneDelegate(IntPtr thisobj); + + // Define the scaling settings struct + [StructLayout(LayoutKind.Sequential)] + public struct ctl_scaling_settings_t + { + public uint Size; // [in] size of this structure + public byte Version; // [in] version of this structure + [MarshalAs(UnmanagedType.U1)] + public bool Enable; // [in,out] State of the scaler + public ctl_scaling_type_flag_t ScalingType; // [in,out] Requested scaling types + public uint CustomScalingX; // [in,out] Custom Scaling X resolution + public uint CustomScalingY; // [in,out] Custom Scaling Y resolution + [MarshalAs(UnmanagedType.U1)] + public bool HardwareModeSet; // [in] Flag to indicate hardware modeset should be done + } + + [Flags] + public enum ctl_sharpness_filter_type_flag_t : uint + { + CTL_SHARPNESS_FILTER_TYPE_FLAG_NON_ADAPTIVE = 1, // Non-adaptive sharpness + CTL_SHARPNESS_FILTER_TYPE_FLAG_ADAPTIVE = 2, // Adaptive sharpness + CTL_SHARPNESS_FILTER_TYPE_FLAG_MAX = 0x80000000 + } + + // Property range details, a generic struct to hold min/max/step size + // information of various feature properties + [StructLayout(LayoutKind.Sequential)] + public struct ctl_property_range_info_t + { + public float min_possible_value; // [out] Minimum possible value + public float max_possible_value; // [out] Maximum possible value + public float step_size; // [out] Step size possible + public float default_value; // [out] Default value + } + + // Sharpness filter properties + [StructLayout(LayoutKind.Sequential)] + public struct ctl_sharpness_filter_properties_t + { + public ctl_sharpness_filter_type_flag_t FilterType; // [out] Filter type. Refer ctl_sharpness_filter_type_flag_t + public ctl_property_range_info_t FilterDetails; // [out] Min, max & step size information + } + + // Various sharpness filter types + [StructLayout(LayoutKind.Sequential)] + public struct ctl_sharpness_caps_t + { + public uint Size; // [in] size of this structure + public byte Version; // [in] version of this structure + public ctl_sharpness_filter_type_flag_t SupportedFilterFlags; // [out] Supported sharpness filters for a given display output. Refer + // ctl_sharpness_filter_type_flag_t + public byte NumFilterTypes; // [out] Number of elements in filter properties array + public IntPtr pFilterProperty; // [in,out] Array of filter properties structure describing supported + // filter capabilities. Caller should provide a pre-allocated memory for + // this. + } + + // Current sharpness setting + [StructLayout(LayoutKind.Sequential)] + public struct ctl_sharpness_settings_t + { + public uint Size; // [in] size of this structure + public byte Version; // [in] version of this structure + [MarshalAs(UnmanagedType.U1)] + public bool Enable; // [in,out] Current or new state of sharpness setting + public ctl_sharpness_filter_type_flag_t FilterType; // [in,out] Current or new filter to be set. Refer + // ctl_sharpness_filter_type_flag_t + public float Intensity; // [in,out] Setting intensity to be applied + } + + [Flags] + public enum ctl_device_type_t : uint + { + CTL_DEVICE_TYPE_GRAPHICS = 1, // Graphics Device type + CTL_DEVICE_TYPE_SYSTEM = 2, // System Device type + CTL_DEVICE_TYPE_MAX = 0 + } + + [Flags] + public enum ctl_supported_functions_flag_t : uint + { + CTL_SUPPORTED_FUNCTIONS_FLAG_DISPLAY = (1 << 0), // [out] Is Display supported + CTL_SUPPORTED_FUNCTIONS_FLAG_3D = (1 << 1), // [out] Is 3D supported + CTL_SUPPORTED_FUNCTIONS_FLAG_MEDIA = (1 << 2), // [out] Is Media supported + CTL_SUPPORTED_FUNCTIONS_FLAG_MAX = 0x80000000 + } + + public struct ctl_firmware_version_t + { + public ulong major_version; // [out] Major version + public ulong minor_version; // [out] Minor version + public ulong build_number; // [out] Build number + } + + [Flags] + public enum ctl_adapter_properties_flag_t : uint + { + CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED = (1 << 0), // [out] Is Integrated Graphics adapter + CTL_ADAPTER_PROPERTIES_FLAG_LDA_PRIMARY = (1 << 1), // [out] Is Primary (Lead) adapter in a Linked Display Adapter (LDA) chain + CTL_ADAPTER_PROPERTIES_FLAG_LDA_SECONDARY = (1 << 2), // [out] Is Secondary (Linked) adapter in a Linked Display Adapter (LDA) chain + CTL_ADAPTER_PROPERTIES_FLAG_MAX = 0x80000000 + } + + [StructLayout(LayoutKind.Sequential)] + public struct ctl_adapter_bdf_t + { + public byte bus; // [out] PCI Bus Number + public byte device; // [out] PCI device number + public byte function; // [out] PCI function + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct ctl_device_adapter_properties_t + { + public uint Size; // [in] size of this structure + public byte Version; // [in] version of this structure + public IntPtr pDeviceID; // [in,out] OS specific Device ID + public uint device_id_size; // [in] size of the device ID + public ctl_device_type_t device_type; // [out] Device Type + public ctl_supported_functions_flag_t supported_subfunction_flags; // [out] Supported functions + public ulong driver_version; // [out] Driver version + public ctl_firmware_version_t firmware_version; // [out] Firmware version + public uint pci_vendor_id; // [out] PCI Vendor ID + public uint pci_device_id; // [out] PCI Device ID + public uint rev_id; // [out] PCI Revision ID + public uint num_eus_per_sub_slice; // [out] Number of EUs per sub-slice + public uint num_sub_slices_per_slice; // [out] Number of sub-slices per slice + public uint num_slices; // [out] Number of slices + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)] + public string name; // [out] Device name + public ctl_adapter_properties_flag_t graphics_adapter_properties; // [out] Graphics Adapter Properties + public uint Frequency; // [out] Clock frequency for this device. Supported only for Version > 0 + public ushort pci_subsys_id; // [out] PCI SubSys ID, Supported only for Version > 1 + public ushort pci_subsys_vendor_id; // [out] PCI SubSys Vendor ID, Supported only for Version > 1 + public ctl_adapter_bdf_t adapter_bdf; // [out] Pci Bus, Device, Function. Supported only for Version > 1 + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 112)] + public string reserved; // [out] Reserved + } + + [DllImport("IGCL_Wrapper.dll")] + private static extern ctl_api_handle_t Init(); + + [DllImport("IGCL_Wrapper.dll")] + private static extern bool Terminate(); + + [DllImport("IGCL_Wrapper.dll")] + public static extern uint GetAdapterCounts(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate IntPtr GetIntelDevicesDelegate(ctl_api_handle_t hAPIHandle, ref uint pAdapterCount); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr GetDevices(ctl_api_handle_t hAPIHandle, ref uint pAdapterCount); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern ctl_result_t GetDeviceProperties(ctl_device_adapter_handle_t hDevice, ref ctl_device_adapter_properties_t StDeviceAdapterProperties); + + // RetroScaling + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t GetRetroScalingCaps(ctl_device_adapter_handle_t hDevice, ref ctl_retro_scaling_caps_t RetroScalingCaps); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t GetRetroScalingSettings(ctl_device_adapter_handle_t hDevice, ref ctl_retro_scaling_settings_t RetroScalingSettings); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t SetRetroScalingSettings(ctl_device_adapter_handle_t hDevice, ctl_retro_scaling_settings_t retroScalingSettings); + + // Scaling + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t GetScalingCaps(ctl_device_adapter_handle_t hDevice, uint idx, ref ctl_scaling_caps_t ScalingCaps); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t GetScalingSettings(ctl_device_adapter_handle_t hDevice, uint idx, ref ctl_scaling_settings_t ScalingSetting); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t SetScalingSettings(ctl_device_adapter_handle_t hDevice, uint idx, ctl_scaling_settings_t scalingSettings); + + // Sharpness + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t GetSharpnessCaps(ctl_device_adapter_handle_t hDevice, uint idx, ref ctl_sharpness_caps_t SharpnessCaps); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t GetSharpnessSettings(ctl_device_adapter_handle_t hDevice, uint idx, ref ctl_sharpness_settings_t GetSharpness); + + [DllImport("IGCL_Wrapper.dll", CallingConvention = CallingConvention.Cdecl)] + static extern ctl_result_t SetSharpnessSettings(ctl_device_adapter_handle_t hDevice, uint idx, ctl_sharpness_settings_t SetSharpness); + + [DllImport("kernel32", SetLastError = true)] + static extern IntPtr LocalFree(IntPtr mem); + + public static IntPtr[] devices = new IntPtr[1] { IntPtr.Zero }; + public static nint deviceIdx = 0; + + public static void Initialize() + { + // Call Init and check the result + ctl_api_handle_t handle = Init(); + if (handle.handle == IntPtr.Zero) + return; + + uint adapterCount = 0; + + // Get the number of Intel devices + IntPtr hDevices = GetDevices(handle, ref adapterCount); + if (hDevices == IntPtr.Zero) + return; + + // Convert the device handles to an array of IntPtr + devices = new IntPtr[adapterCount]; + Marshal.Copy(hDevices, devices, 0, (int)adapterCount); + if (devices.Length == 0) + return; + + for (int idx = 0; idx < devices.Length; idx++) + { + ctl_device_adapter_properties_t StDeviceAdapterProperties = new(); + ctl_adapter_properties_flag_t adapterFlag; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = devices[idx] + }; + + ctl_result_t Result = GetDeviceProperties(hDevice, ref StDeviceAdapterProperties); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + continue; + + switch (adapterCount) + { + default: + case 1: + deviceIdx = idx; + return; + + case 2: + case 3: + case 4: + { + adapterFlag = StDeviceAdapterProperties.graphics_adapter_properties; + if (!adapterFlag.HasFlag(ctl_adapter_properties_flag_t.CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED)) + { + deviceIdx = idx; + return; + } + } + break; + } + } + } + + internal static bool HasGPUScalingSupport(nint deviceIdx, uint displayIdx) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_scaling_caps_t ScalingCaps = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetScalingCaps(hDevice, displayIdx, ref ScalingCaps); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return ScalingCaps.SupportedScaling >= 0; + } + + internal static bool GetGPUScaling(nint deviceIdx, uint displayIdx) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_scaling_settings_t ScalingSettings = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetScalingSettings(hDevice, displayIdx, ref ScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return ScalingSettings.Enable; + } + + internal static bool SetGPUScaling(nint deviceIdx, uint displayIdx, bool enabled = true) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_scaling_settings_t ScalingSettings = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetScalingSettings(hDevice, displayIdx, ref ScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // skip if not needeed + if (ScalingSettings.Enable == enabled) + return true; + + // fill custom scaling details + ScalingSettings.Enable = enabled; + + Result = SetScalingSettings(hDevice, displayIdx, ScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // check if value was properly applied + Result = GetScalingSettings(hDevice, displayIdx, ref ScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return ScalingSettings.Enable == enabled; + } + + internal static bool SetImageSharpening(nint deviceIdx, uint displayIdx, bool enable) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_sharpness_settings_t GetSharpness = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetSharpnessSettings(hDevice, displayIdx, ref GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // skip if not needeed + if (GetSharpness.Enable == enable) + return true; + + // if disabled, we need to set intensity to 0 first (while enabled) + switch (enable) + { + default: + case false: + GetSharpness.Intensity = 0; + Result = SetSharpnessSettings(hDevice, displayIdx, GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + break; + } + + // fill custom scaling details + GetSharpness.Enable = enable; + + Result = SetSharpnessSettings(hDevice, displayIdx, GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // check if value was properly applied + Result = GetSharpnessSettings(hDevice, displayIdx, ref GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return GetSharpness.Enable == enable; + } + + internal static bool SetScalingMode(nint deviceIdx, uint displayIdx, int mode) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_scaling_settings_t ScalingSettings = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetScalingSettings(hDevice, displayIdx, ref ScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // 0: aspect + // 1: full + // 2: center + ctl_scaling_type_flag_t ScalingType = ctl_scaling_type_flag_t.CTL_SCALING_TYPE_FLAG_IDENTITY; + switch (mode) + { + case 0: + ScalingType = ctl_scaling_type_flag_t.CTL_SCALING_TYPE_FLAG_ASPECT_RATIO_CENTERED_MAX; + break; + case 1: + ScalingType = ctl_scaling_type_flag_t.CTL_SCALING_TYPE_FLAG_STRETCHED; + break; + case 2: + ScalingType = ctl_scaling_type_flag_t.CTL_SCALING_TYPE_FLAG_CENTERED; + break; + } + + // skip if not needeed + if (ScalingSettings.ScalingType == ScalingType) + return true; + + // fill custom scaling details + ScalingSettings.ScalingType = ScalingType; + + Result = SetScalingSettings(hDevice, displayIdx, ScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // check if value was properly applied + Result = GetScalingSettings(hDevice, displayIdx, ref ScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return ScalingSettings.ScalingType == ScalingType; + } + + internal static bool GetImageSharpening(nint deviceIdx, uint displayIdx) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_sharpness_settings_t GetSharpness = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetSharpnessSettings(hDevice, displayIdx, ref GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return GetSharpness.Enable; + } + + internal static int GetImageSharpeningSharpness(nint deviceIdx, uint displayIdx) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return 0; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + ctl_sharpness_settings_t GetSharpness = new(); + + Result = GetSharpnessSettings(hDevice, displayIdx, ref GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return 0; + + return (int)GetSharpness.Intensity; + } + + internal static bool SetImageSharpeningSharpness(nint deviceIdx, uint displayIdx, int sharpness) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_sharpness_settings_t GetSharpness = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetSharpnessSettings(hDevice, displayIdx, ref GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // skip if not needeed + if (GetSharpness.Intensity == sharpness) + return true; + + // fill custom scaling details + GetSharpness.Intensity = sharpness; + + Result = SetSharpnessSettings(hDevice, displayIdx, GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // check if value was properly applied + Result = GetSharpnessSettings(hDevice, displayIdx, ref GetSharpness); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return GetSharpness.Intensity == sharpness; + } + + internal static bool HasIntegerScalingSupport(nint deviceIdx, uint displayIdx) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_retro_scaling_caps_t RetroScalingCaps = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetRetroScalingCaps(hDevice, ref RetroScalingCaps); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return RetroScalingCaps.SupportedRetroScaling >= 0; + } + + internal static bool GetIntegerScaling(nint deviceIdx) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_retro_scaling_settings_t RetroScalingSettings = new(); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetRetroScalingSettings(hDevice, ref RetroScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return RetroScalingSettings.Enable; + } + + internal static bool SetIntegerScaling(nint deviceIdx, bool enabled, byte type) + { + ctl_result_t Result = ctl_result_t.CTL_RESULT_SUCCESS; + ctl_retro_scaling_settings_t RetroScalingSettings = new(); + ctl_retro_scaling_type_flags_t RetroScalingType = (ctl_retro_scaling_type_flags_t)(type + 1); + + IntPtr device = devices[deviceIdx]; + if (device == IntPtr.Zero) + return false; + + ctl_device_adapter_handle_t hDevice = new() + { + handle = device + }; + + Result = GetRetroScalingSettings(hDevice, ref RetroScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // skip if not needeed + if (RetroScalingSettings.Enable == enabled && RetroScalingSettings.RetroScalingType == RetroScalingType) + return true; + + // fill custom scaling details + RetroScalingSettings.Enable = enabled; + RetroScalingSettings.RetroScalingType = RetroScalingType; + + Result = SetRetroScalingSettings(hDevice, RetroScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + // check if value was properly applied + Result = GetRetroScalingSettings(hDevice, ref RetroScalingSettings); + if (Result != ctl_result_t.CTL_RESULT_SUCCESS) + return false; + + return RetroScalingSettings.Enable == enabled; + } + } +} \ No newline at end of file diff --git a/HandheldCompanion/IGCL_Wrapper.dll b/HandheldCompanion/IGCL_Wrapper.dll new file mode 100644 index 0000000000000000000000000000000000000000..e11546f8b56f97f50702be1fac408626c1955744 GIT binary patch literal 103424 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%0A)~UxknjDgFn16O1 zr&T|ejy1msJzq+|FP|rFvdY`^>%S)b!(>-~OjqixJxqSHPcZti`E^?H!=-x9U&w))caUnAfn-%xB~~+*!DN zM~GjY^!4OF-1bhCPiP>af&V)iC}kuns&4e!63KYPPb{gXQ@`GLVvpBr53zpfrB~ae zHFV?akNBTTRlpZ#~B@{whk7s)s; z>#CD#>Kg5BAc-@NoyHonH08tVzk0T5 zo==nyr^i|Uj?6C{HL19|wz#~yq0yorJHxd3vYxWxS*2B8W}BV{E4QFlHehUh7_qE! zmwOCm8(-*+Bs7mR@^^k;AU*DT1nID!S5d)YgEUEG{L)%WER&u$vm~kV8;%T_F zo;Iz%Rwe%u(W!qikR!ztPWk1dE2j0jM z;dHxxc={dQiW4$ceY^e4hMkoz%ywFbHuQ?}CKXtMXe_pVDmxgex1oVpkq- z{<7NgMb#CwqPL;E(OXccwWeSL72s7tu>JgTx8LLS$vbmq`!z58}e7XF<8I#%n9u)F=HSdSB)AdXJ}4XNA&r z(hS*8jYt=fz3ePne8NwI=Ng}qYcb(|WsA{P5dTTsl8n*tVxZSU3RabiIP^Ao~jB}x(!5YOiJ}K|< z#+0-lR`4h7W40~Pn2)VaAw;@Ki%~{ox}{Q^uTc4kEQKtouGG(5%3sP7N`4F7~9xs@5ZO!Ka${AczG>VUU->kYCFC9CK?Lhg0B__Yf zh|>wLq-&y)j zsa0~Ob4ht4C*2wve5~_Vj?{^GGh&LK>8x&a&iBafg0GP?a?EL~>U>@+shT-*HHYf1 zjlf`IIxsYJYJ47N-2!L9^kU6UZJpOyuP1Re%bbnnyz@HCuNdWXBDcbt6eNVH3(`E zF0xX6T{SNT4bJGw<<83L1q(cKASMoyk!ca9ybi2`$lN?oHF}-w+>|@n*r}-zvgO`# zrrKMM}v=5rqqAUlbwjDeP)d%4^PXF01o78)>YXO6Q{T#;dgz zG}ieVDzwhUCNxPcw8S`0hVHQvug5vR+ADQH&#RErvs9ls8|!qY_32%~CY-avIl(!s z+Ia@2Brm$?tZ`|5^s0sET;t^}1YT^umZ8|18mF{wIf|=5OvqhByD=#jNhE4oPcYP( z_i5Tn)&ahzK5gBaez3@#f{QZw=yu9Otyo03n@Cx$@cABIrxC|%X*gu@Q3#^wSlzCa zW&~+;Pm8Lh)XdteYwMQOI<1u9Pv212yAk)*auI-!RdNU&8bEZmKCL&FqIyM)F$XRQb8^;t@{$HWMBhK?Ln;v{a5pPq7i z5Vc#=vL)+FEe%?8ldQlA$pz00!g?40)6u7yIlXk8Ff6Qh`*53M!lxKjFcKMkI+syh+a((<1QyhPV)=)9BvcDmYn8Ucxk<-cCu$# z!P41OBYv6B)39t-Idcb-q>+)X*V`$mm>-yL+t(jG{D^v-n7_f^+a&dvAC_br@Tc=4p7QtL9+ z$ofii3_zU-J#q4Pya5S6p@BozfMcMFI=31dKV(FSf(Z@$8*1R0nEn$R|2I@gLgWKz zz|qs8dU8<3(bu8+3QTgSB!NSc)R1YJ^^S9aw*&hmsXn86);rRYRN8>v^`Ua z-+D(ZJS#~ZdwY8Qkh~shkYj*K2j~08a843lV$)WW=CI0lf-!i9P_Fmilj;Q*D3zmU zlIl54IF9}sGQ5Wx09}#kjLobaPL}t@+#l|>SiBCA__Ut&LiZO!sO%ZRt}7p7m65zx zByZRs@%Ko6fAt%x7+pL1C#n9^`qVoF_cU5>dspEU*=>34<4&hH8yYQ0!~GLlrrw0_|+p;MgxbU*#644brRcPeu*95MN^JlJ>- zRfYqVYmiZ-d;awJ`UL$ry{Af_k*d;1_fqMjL^DdAnv$eaygikZMXzzV*DsYZjC5RV znxj__)oWsp`lM`KM4j3%S@ruQS*3t;H+5gb4KB9o3*O4!I_)FUhef8p+eb;-m$-a8 z9hC9X1ZPxz%Ei)hBs)|xL*J=*$sG3DeS=be!EK1Z9h;`>@ID!W$2!!p_Y)uNP=hBL z`9zXq`a4wro0K|-u~g#MtW@d~TsGsV{imM~qmqh`Np+}Ha7x)EGLvtn9In_Z`^75u z6p3FIr>>2|F~Fe)fJ4KH79`$V(r*g$u?0L&-ohCOa0!$@>p4|y*Bx?7ib@;ZqS72U z>*ueG(N4x_`5TheOAV;>s{y0G#Tb5bXbcbKpVE_h7pk6nlIwevB^68EJ*H{Zsi{dS zb@T;lcZ*WzehWFLS#1v9*fht$R5h@&j~Y0mw;DK_Fk`&XcZgn*YlyTRa*nd}`^j1! zj<<353b_R8vhw^zzq+mNr>@ii4yoy@YAS#4IM(q8N56f2_N4Ca*#@`8VxPkA+WuDk zj1+mc?(4HVwXJ8FNjvqVRCUt3$tu&ErqX8gQz@f)B)2Lx0=GLtp4c>pv@!fTj&Z1C z=3Bf-ythO3mbkz_#X!jyo+V#oX-KNK+eoL|`8Jetk@#*3$9qfq{b4((`+-ug;+%gm zu#>)|uFbOz`#Dsn$&kePFv2iJPnpIxt+HqUwHa|ALNNAUj2W3o< z^n<|@daDy0gH-ySwB3E%de^6x^~&y9Z0u~v`F&N^yVF&ccbdwoyi8?{E>>BNsVb9v zvC*<5tK3N`s;}cErF)xYzhynGJf8Kv9DZHiW%u7q?CAESY{B6&#Z0@M2ETUPf(ek& z!2h%cg8T{17q4_^d*m^p#8s6z)Y{7(YU5mos?AX9bll&MW$Xfef-OHiK&dBi8&^2g z2Aq?;CgM8@f1mIQ%3O;3G2tb^EH>m)sGCZe;|YJ0a0ajgcq3&tQsxTc&k}xtGT*`t zA@3{Fe?OD?+4X{7(V#+@Z z+)3VPz#X`E$@`M>RfNBXyBRm0{8^N3#d&bm#D7A$GjNj$pNzYZ@M7F@$`7UfJET2C z{7&NM62Akt61SbSTgWTL4I=&^@IKP|g7+F=Bk}#f>w*7C-OnhqlyEcoXA)mX-dm)7 zPS}O>l70;F_Xz)zysL;$A$~S4hj1EcSK?+9{{XlO_iMu0!2iaE#?N!8dvJfnjVN`f z2HekZAK`|SIn+0CD{v3u-o`n~9cn0UD()uS&v83&uj2lK`wEvf-=Uni^Kcj8CgUn_ zi*bJ3owz4(n{m(MUdO$M`vR9-;ZTEc!*LUFvvBiq*W$j7`yuW>aj)R^;*u*J>IB?y z+=aNQxC-2LxI1u9;9kVNgZmUWz~fM7;)-yUxMjGtxF6$wh1-MsC$8@T%HhW0X5$v) zT5$K{p2EF^`#Uamp+gPBjmKS%Yr_2i_ag33xQ}pO;RaT*?~TjB72?Wp4Y)P9$8o!H zf5m-;JGt7SF2I%G8gQ++pWuFj+lM>$Du)`4n~A#`w-R?3?#H+txWD4m)yRq)h8v5! z6jzE{f?I?8A?`nMFX8@%3*!3MpiA60aZ_;%aZNaR@+GMriZ>G0IC^nLxHk(eeff4o zn(D98xx;#l8laA44?j~4Qpc&mY6$yBC$N`vk~&$PqMYhfzEF8OcmIc~Gt`-Cm^w?H zt%j>}I4d(!ovX4rF*AyjFr(G^oK^WI-^d)pmpjL)@oIv)KwZdlIFEBZlQ=V*?-7|$VEthic=Q7T_Oy}gwOircDRwZhVx?Eks8I>#5JXNa7l$;B!&}Uy3 zaAK#5lPFhlim-;0J++*PsaMx>vR3op#N@EbEP%#x#`rCjH#_BOimU5(yKm&^Eb^70DuW?h&y zzr3-!qO^|71D^6luKX^lWV@vFdD*Vwf{FRA%LToswz8(n@|w`sB%#q~mvT9RJ0PX- z=W!h>o0{xp1$lXf-BB)st5KsmLM-PUAx9;mwe!9qb4nv-{7UZ>I_8gfd#lmq^af`io3+0ru8y$ziCn>4=|;9W3VkX)iOODAJzrL!-m zOsU-Mt*dRE&P`CRXSpss@4^d9OZ7?NQkuKEu9WlA)Gn1X%an7u#<)h8PKM9YQZ6BR zmz6H8sheM3Q(DQjA}@l^$t!_IiO@fAiPFhZ##6Td!zrCw&UxS^rBU6UIlq4%#mCRvo>T&nNqhwq7F;BhY9ORD}f0D+i0y^>;XxJKMsjZBQVrK*!-Zp2z? z`gW@wwr`Qzx6Z6|b34+$VQD2sT~D*pBCdTgD(Wj^R*v~NuJzG?sI=%021I7Xd@Dfi zq-it}cd98K=jtsc6D_L!pJqzH=S(4Um}JNNUu!KerH5 zDm)r@Q6PrB5GJ|UZE(eYGr**VzSu!D{GpA=NcbxolFFl6@WuPq0LgK#bVsH}8)&F- zsPBbuX2%XD;hea4yCHIb612PU99e?v0CK%NUUHn$QJ13QREx@wt*<*m+KBkmsl^(p@5O6RK! zerNW7BC+`HGBLAKYxXJau;kc#Mv`W>Mdd3G`p-nIh~y{!x=#a5LzEiB#fr6;IFt+6 zaDq~I5pDuL!NrU9gxi3>JDI&B!h3*A*mvF_Wq@nWP-+|D^}r{Gai&Ph0}HUJJ%o#a zZ6nz4BD@>8l#A{A2sZ(LdM^7)QXcs2Y-F4a9^k9E48j|}!G79U&Jz)Kje|$rI!Onf zI-a|Ek`6p}0yGF010TgbKzKLsv=0#`xFa2)6;< zTvQ!TxEQz{mrr;%aAZDg5_SRa!_6SP0ocDlskwxmz#DLtgx3Q1;%W%938AWRUc&Xj z*Kx}U?*Y0mW}lC6G4OHR8p3VBlilc(a5nH(+`WX?15*ppA>j;QE$%VGO~AKsPZHh- zoO%g+dxXn?n{nF+?*@*X%zi3i7w|sZ?+9-I_UGF3e-U;9Z@|4vcrEY)++M%hKH=TKk<-vWVHfZ|+zi4Sfc>YVf5J}SYFs7ZwZIQ>HH6vd zQS)$K!u7zHaLWnr0bVc@{Sz(*K8{;MxD9ynEc8z}8+a@3Uc&2v1Gr%Q0AVNa2HazW z*8)GlJxQ3&BsBx~OTuNq&A4rZcLPVxLH~qZz=v?ZBfJ6F|8n$C*a^G=_b%bJ!2P(r zgxQ)>^KhRKt_Qw^`;zb;;OM#Ne=0bE_u={x-T>@>CHg1q1m280k?>mJeq0t|HoDY2 z+&P5nfv@966W#;7pcMTRE(Si1%O~6hJh=@06V3+Sikm@rJ@6~sT*4XUv?;EVa1$_q zt0BA(ICVbyCtL>Hj9X54H*jPH`X}rH-iKR5cmpuG68#g-0M_E}CENtugL{DRKHyXj z`X^ilY{NZCcsFq50`yPV1$+{>jc^MJ%lr=7$hWx&n2T*AA7BNwq(PuK;#59cPl0ocD5J_tL3t8pcS*8)Gll@VrZQq99v5v~VL zsz)Be#mgCQuSa&mWx(~g4EpZ|;IJFupK#>4OgXdJ^G42WaBfpzU)*lOk>^n5jOuN; zz2Fr116(p?BF}_Io*9+1qxLydf$k=3hVlZZ;Jkzdmf=X4Xn5sol6Bv48^UHbl@|%l~NwqvjsboGQe55yEva%4}220o^a%uOF563>ZjdF7dRA`OL!mf zS8L!OJdtM_<-FrvxOXWd@CjV%bjsYS)a7d#FQhIo{Wkg(VJGlC+)m-=c6h`Uk-i@I zYg{GawRaH46@h0D@QHQwal-q6J?^CM6V3oWhC5Np06+W=;{##WcR3S-8&6o>MO|FJ zlmXs%H*H6FH!%Nuj01%0fe+y-3AX|NfUA-6z<=Ppgfs5p+%0aoln1WDWzT?5;H-PG z2?+!5!{rj*037>$`U2s4;H$Wu)ZGU>=|1#BdNyzhP7y8xuEjYCuLu4DH;ix_aOi)~ zf2r#N*5WcqZvx(l8!Y94?YP$|qt;`~KVTdnTnu~@cdw-1&)FQ@Zovb*5x0l%T434_ zu^GbI!0T}9BptXFx00}WK&jgwWUc}&^&{pyxMITfz#DK?!UOQ4AJYdU9e6XYUebYM zAEI9nE(6|=TT8eNnDj9063zxr#chx>z@@m2gqwih#{~#)0R9%YkMKU=phwUNVJEN{ z_d4M+;LSKS6aIm(;Pyy);1!P|JK-kagSa-r8-O3;GAOSeV{VBXOt=ZS6Xztn2RQ5} zIKnRA!?;w!yMetPryO{)fopz>T%_;&f9+jgY*SSjKigUY1kHlc5dS0%D%}IHZbMj> zjTZJN-56vUV-TWTyY_Y~tiAQ#+tEFcEQ(4DiF;792SQpzEn9~OgiL~nhzSouG~t2x zBJp9Fi7^->55|AL@1E~=Y!tUKNQ|_}-S3?5oco<~zH{#BS?~Fs4|w<_#x25ar+}w1 zeh?l6c8+1pko;4Ot8*|b<^N2G$Iqj^2z#z!4>PU{ln2;}>oUT`{&@|Ti8Ut?f4;}H zA7SF*Jd0}rVGpncmj};O8<6&g_aaOXIRc9NyaxAhZwJ(l<7AZZ!s=8CFL&08pbV*yBQBK9%4Mf_%-7=;~$Jm zPfNWk8U2jA7~_ol8Q)<{F=iRhGoI!8&eKfuzI}}C`5oi$j8Bfp_SP`I$at7>oU!#| znQtNErQ&WmfzxdFURNIzv%&B44e<}8ok;C4J&P&q`TlTH zb^!g`lg9e%qeQY^QZ1o_llaJoxcwLpl| zt@A}UHMVbQY^|zXP0R+d!s*39{s8j= zcptpbO1xq6Go2>`ZRHkCPOs&xBu~21f((s!OW&|TX;E6RvwPg(HR1^~nByEsennRz z{O^v=q zt2bMVL1Zw($y~8B5pHr!mK`X1Ns-sP*0#*7?ADBSYo-hP9xF&J}xu@W~)& zp{iLAe)I``U*6hT-&V24(_l?+>jh3X#}iAlucFi^lTC2hqe|9}z6QT+l2kF`YA_N8 zaZ3_+bl5VlT#0FNiWJWy9Tm6uDIltZH4tL(%xL{eujPaCDWnJGro_ov6`Z*|QU(4R zOf_kOEvG1bLETg^g-aR(ogGgH#BT?WZhD$*!*;OiIAz*2-&xV*j zSdK{|Dx9zacsTei*10!m#DejD#Q+~~dVa$un}Bzy)azzLS9iM_*K|`jr^fN7)+~aB zBPRS-L`7mUtf*ool_rZ2WmY&~qBw8>?RtQcyKf>{d+75WN1Bd81DQxNCv zD>2DT0=cRWoLyO(bO>T6sj$(t^sF0eViUZ6;PVaJqL5|Q8@+yWt9PAEYc+THSg2T|`%jX!YTBMe zyoo)ZG!i8dZ*QDP>5S+m{AX;7?TyeI}{%UIi7^uKHFJU)t%a#Lt@=+Qcz&j zKnOyyxCAxX+K>K^l4FMr20hM}pSd+fpj*%&k(qgMR*#3z5O0+bWk3$Ot>nSS4|U=f zDgj%AVWc?ubKEZ8C5fCS?5RNPBUvjP0$A=g@C=sV=D4+9#Jo&lS}S5N1Lfn!lN4`u z7H|hx4ac`L&6^l4_iNQ!&{MF$|Jwp(rBlzxogV*tTKbnW#HU;9*UzWwpPpWb|CcO) z^UzQoTmw^UB~P35d6zIk3tIpl88gB3UM;6xx3`31}!NVF9#Ed$3LXXuL!5$U< z!B)XxFWj?%$g>J7+nRt8UENpVhPSa;B#iZa=k29ImCIFY>L63nH!^EmK7+QZ<<@<; z)4=jihrck6Lt+tWv0MnX!KXRa3f-!aOT0a~(ONlzp5LnWslBdV`djA?7|pRhZI7zE zU5SYFbzSEU2YLIDYfNO>Qq~vYaDK`Ew}B4$U{E%3e7KA;0_Cv;yfM* z%Z7V~hlVr5%SQYoJ4P;#OpKIe{n-QAOm-r>aMV94TsSWX`G(Th)60(RI5II>c3gPy z?hCR^Y9h5P?Mj!WJ?ZMSFI|@LWU4d1j6dT!R(PZyg-^i(1q(c23;Y99+ZHDP literal 0 HcmV?d00001 diff --git a/HandheldCompanion/Managers/Desktop/DesktopScreen.cs b/HandheldCompanion/Managers/Desktop/DesktopScreen.cs index 3f37fc492..65a01f030 100644 --- a/HandheldCompanion/Managers/Desktop/DesktopScreen.cs +++ b/HandheldCompanion/Managers/Desktop/DesktopScreen.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Forms; -using static HandheldCompanion.Managers.SystemManager; +using static HandheldCompanion.Managers.MultimediaManager; namespace HandheldCompanion.Managers.Desktop; diff --git a/HandheldCompanion/Managers/DynamicLightingManager.cs b/HandheldCompanion/Managers/DynamicLightingManager.cs index 747228bbb..8e2a2e431 100644 --- a/HandheldCompanion/Managers/DynamicLightingManager.cs +++ b/HandheldCompanion/Managers/DynamicLightingManager.cs @@ -47,7 +47,7 @@ static DynamicLightingManager() rightLedTracker = new ColorTracker(); SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; - SystemManager.DisplaySettingsChanged += SystemManager_DisplaySettingsChanged; + MultimediaManager.DisplaySettingsChanged += SystemManager_DisplaySettingsChanged; MainWindow.CurrentDevice.PowerStatusChanged += CurrentDevice_PowerStatusChanged; ambilightThread = new Thread(ambilightThreadLoop); @@ -60,7 +60,7 @@ static DynamicLightingManager() DynamicLightingTimer.Elapsed += (sender, e) => UpdateLED(); } - public static void Start(bool service = false) + public static void Start() { IsInitialized = true; Initialized?.Invoke(); diff --git a/HandheldCompanion/Managers/GPUManager.cs b/HandheldCompanion/Managers/GPUManager.cs new file mode 100644 index 000000000..5884e00ea --- /dev/null +++ b/HandheldCompanion/Managers/GPUManager.cs @@ -0,0 +1,205 @@ +using HandheldCompanion.ADLX; +using HandheldCompanion.Controls; +using HandheldCompanion.GraphicsProcessingUnit; + +namespace HandheldCompanion.Managers +{ + public static class GPUManager + { + #region events + public static event InitializedEventHandler Initialized; + public delegate void InitializedEventHandler(GPU GPU); + #endregion + + public static bool IsInitialized; + + private static GPU currentGPU = new(); + + static GPUManager() + { + // initialize processor + currentGPU = GPU.GetCurrent(); + currentGPU.ImageSharpeningChanged += CurrentGPU_ImageSharpeningChanged; + currentGPU.GPUScalingChanged += CurrentGPU_GPUScalingChanged; + currentGPU.IntegerScalingChanged += CurrentGPU_IntegerScalingChanged; + + if (currentGPU is AMDGPU) + { + ((AMDGPU)currentGPU).RSRStateChanged += CurrentGPU_RSRStateChanged; + } + else if (currentGPU is IntelGPU) + { + + } + + // manage events + ProfileManager.Applied += ProfileManager_Applied; + ProfileManager.Discarded += ProfileManager_Discarded; + ProfileManager.Updated += ProfileManager_Updated; + } + + private static void CurrentGPU_RSRStateChanged(bool Supported, bool Enabled, int Sharpness) + { + // todo: use ProfileMager events + Profile profile = ProfileManager.GetCurrent(); + AMDGPU amdGPU = (AMDGPU)currentGPU; + + if (Enabled != profile.RSREnabled) + amdGPU.SetRSR(profile.RSREnabled); + if (Sharpness != profile.RSRSharpness) + amdGPU.SetRSRSharpness(profile.RSRSharpness); + } + + private static void CurrentGPU_IntegerScalingChanged(bool Supported, bool Enabled) + { + // todo: use ProfileMager events + Profile profile = ProfileManager.GetCurrent(); + + if (Enabled != profile.IntegerScalingEnabled) + currentGPU.SetIntegerScaling(profile.IntegerScalingEnabled, profile.IntegerScalingType); + } + + private static void CurrentGPU_GPUScalingChanged(bool Supported, bool Enabled, int Mode) + { + // todo: use ProfileMager events + Profile profile = ProfileManager.GetCurrent(); + + if (Enabled != profile.GPUScaling) + currentGPU.SetGPUScaling(profile.GPUScaling); + if (Mode != profile.ScalingMode) + currentGPU.SetScalingMode(profile.ScalingMode); + } + + private static void CurrentGPU_ImageSharpeningChanged(bool Enabled, int Sharpness) + { + // todo: use ProfileMager events + Profile profile = ProfileManager.GetCurrent(); + + if (Enabled != profile.RISEnabled) + currentGPU.SetImageSharpening(profile.RISEnabled); + if (Sharpness != profile.RISSharpness) + currentGPU.SetImageSharpeningSharpness(Sharpness); + } + + public static void Start() + { + currentGPU.Start(); + + IsInitialized = true; + Initialized?.Invoke(currentGPU); + + LogManager.LogInformation("{0} has started", "GPUManager"); + } + + public static void Stop() + { + if (!IsInitialized) + return; + + currentGPU.Stop(); + + IsInitialized = false; + + LogManager.LogInformation("{0} has stopped", "GPUManager"); + } + + private static void ProfileManager_Applied(Profile profile, UpdateSource source) + { + try + { + // apply profile GPU Scaling + // apply profile scaling mode + if (profile.GPUScaling) + { + if (!currentGPU.GetGPUScaling()) + currentGPU.SetGPUScaling(true); + + if (currentGPU.GetScalingMode() != profile.ScalingMode) + currentGPU.SetScalingMode(profile.ScalingMode); + } + else if (currentGPU.GetGPUScaling()) + { + currentGPU.SetGPUScaling(false); + } + + // apply profile RSR + if (currentGPU is AMDGPU amdGPU) + { + if (profile.RSREnabled) + { + if (!amdGPU.GetRSR()) + amdGPU.SetRSR(true); + + if (amdGPU.GetRSRSharpness() != profile.RSRSharpness) + amdGPU.SetRSRSharpness(profile.RSRSharpness); + } + else if (amdGPU.GetRSR()) + { + amdGPU.SetRSR(false); + } + } + + // apply profile Integer Scaling + if (profile.IntegerScalingEnabled) + { + if (!currentGPU.GetIntegerScaling()) + currentGPU.SetIntegerScaling(true, profile.IntegerScalingType); + } + else if (currentGPU.GetIntegerScaling()) + { + currentGPU.SetIntegerScaling(false, 0); + } + + // apply profile image sharpening + if (profile.RISEnabled) + { + if (!currentGPU.GetImageSharpening()) + currentGPU.SetImageSharpening(profile.RISEnabled); + + if (currentGPU.GetImageSharpeningSharpness() != profile.RISSharpness) + currentGPU.SetImageSharpeningSharpness(profile.RISSharpness); + } + else if (currentGPU.GetImageSharpening()) + { + currentGPU.SetImageSharpening(false); + } + } + catch { } + } + + private static void ProfileManager_Discarded(Profile profile) + { + try + { + /* + // restore default GPU Scaling + if (profile.GPUScaling && currentGPU.GetGPUScaling()) + currentGPU.SetGPUScaling(false); + */ + + // restore default RSR + if (currentGPU is AMDGPU amdGPU) + { + if (profile.RSREnabled && amdGPU.GetRSR()) + amdGPU.SetRSR(false); + } + + // restore default integer scaling + if (profile.IntegerScalingEnabled && currentGPU.GetIntegerScaling()) + currentGPU.SetIntegerScaling(false, 0); + + // restore default image sharpening + if (profile.RISEnabled && currentGPU.GetImageSharpening()) + currentGPU.SetImageSharpening(false); + } + catch { } + } + + // todo: moveme + private static void ProfileManager_Updated(Profile profile, UpdateSource source, bool isCurrent) + { + ProcessEx.SetAppCompatFlag(profile.Path, ProcessEx.DisabledMaximizedWindowedValue, !profile.FullScreenOptimization); + ProcessEx.SetAppCompatFlag(profile.Path, ProcessEx.HighDPIAwareValue, !profile.HighDPIAware); + } + } +} diff --git a/HandheldCompanion/Managers/HotkeysManager.cs b/HandheldCompanion/Managers/HotkeysManager.cs index 7b8894788..aa0733db3 100644 --- a/HandheldCompanion/Managers/HotkeysManager.cs +++ b/HandheldCompanion/Managers/HotkeysManager.cs @@ -527,7 +527,7 @@ public static void TriggerRaised(string listener, InputsChord input, InputsHotke LogManager.LogDebug("Executed Hotkey: {0}", listener); // play a tune to notify a command was executed - SystemManager.PlayWindowsMedia("Windows Navigation Start.wav"); + MultimediaManager.PlayWindowsMedia("Windows Navigation Start.wav"); // raise an event CommandExecuted?.Invoke(listener); diff --git a/HandheldCompanion/Managers/LayoutManager.cs b/HandheldCompanion/Managers/LayoutManager.cs index d075be267..01f88c47f 100644 --- a/HandheldCompanion/Managers/LayoutManager.cs +++ b/HandheldCompanion/Managers/LayoutManager.cs @@ -65,7 +65,7 @@ static LayoutManager() SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; - SystemManager.DisplayOrientationChanged += DesktopManager_DisplayOrientationChanged; + MultimediaManager.DisplayOrientationChanged += DesktopManager_DisplayOrientationChanged; } public static FileSystemWatcher layoutWatcher { get; set; } diff --git a/HandheldCompanion/Managers/MultimediaManager.cs b/HandheldCompanion/Managers/MultimediaManager.cs new file mode 100644 index 000000000..45fcdcb0c --- /dev/null +++ b/HandheldCompanion/Managers/MultimediaManager.cs @@ -0,0 +1,604 @@ +using HandheldCompanion.ADLX; +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.Threading; +using System.Timers; +using System.Windows.Forms; +using Timer = System.Timers.Timer; + +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": + { + var nativeOrientation = (ScreenRotation.Rotations)Convert.ToInt32(value); + + if (!IsInitialized) + return; + + var oldOrientation = screenOrientation.rotation; + screenOrientation = new ScreenRotation(screenOrientation.rotationUnnormalized, nativeOrientation); + + if (oldOrientation != screenOrientation.rotation) + // Though the real orientation didn't change, raise event because the interpretation of it changed + DisplayOrientationChanged?.Invoke(screenOrientation); + } + break; + } + } + + private static void HotkeysManager_CommandExecuted(string listener) + { + switch (listener) + { + case "increaseBrightness": + { + var stepRoundDn = (int)Math.Floor(GetBrightness() / 5.0d); + var brightness = stepRoundDn * 5 + 5; + SetBrightness(brightness); + } + break; + case "decreaseBrightness": + { + var stepRoundUp = (int)Math.Ceiling(GetBrightness() / 5.0d); + var brightness = stepRoundUp * 5 - 5; + SetBrightness(brightness); + } + break; + case "increaseVolume": + { + var stepRoundDn = (int)Math.Floor(Math.Round(GetVolume() / 5.0d, 2)); + var volume = stepRoundDn * 5 + 5; + SetVolume(volume); + } + break; + case "decreaseVolume": + { + var stepRoundUp = (int)Math.Ceiling(Math.Round(GetVolume() / 5.0d, 2)); + var volume = stepRoundUp * 5 - 5; + SetVolume(volume); + } + break; + } + } + + private static void onWMIEvent(object sender, EventArrivedEventArgs e) + { + var brightness = Convert.ToInt32(e.NewEvent.Properties["Brightness"].Value); + BrightnessNotification?.Invoke(brightness); + } + + private static void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) + { + Screen PrimaryScreen = Screen.PrimaryScreen; + + if (desktopScreen is null || desktopScreen.PrimaryScreen.DeviceName != PrimaryScreen.DeviceName) + { + // update current desktop screen + desktopScreen = new DesktopScreen(PrimaryScreen); + desktopScreen.devMode = GetDisplay(desktopScreen.PrimaryScreen.DeviceName); + + // pull resolutions details + List resolutions = GetResolutions(desktopScreen.PrimaryScreen.DeviceName); + foreach (Display 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) + { + var 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(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; + var 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; + + var ret = false; + long RetVal = 0; + var dm = new Display(); + dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + dm.dmDisplayFrequency = displayFrequency; + dm.dmFields = Display.DM_PELSWIDTH | Display.DM_PELSHEIGHT | Display.DM_DISPLAYFREQUENCY; + 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; + + var ret = false; + long RetVal = 0; + var dm = new Display(); + dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + dm.dmDisplayFrequency = displayFrequency; + dm.dmBitsPerPel = bitsPerPel; + dm.dmFields = Display.DM_PELSWIDTH | Display.DM_PELSHEIGHT | Display.DM_DISPLAYFREQUENCY; + RetVal = ChangeDisplaySettings(ref dm, CDS_TEST); + if (RetVal == 0) + { + RetVal = ChangeDisplaySettings(ref dm, 0); + ret = true; + } + + return ret; + } + + public static Display GetDisplay(string DeviceName) + { + var dm = new Display(); + dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); + bool mybool; + mybool = EnumDisplaySettings(DeviceName, -1, ref dm); + return dm; + } + + public static List GetResolutions(string DeviceName) + { + var allMode = new List(); + var dm = new Display(); + dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); + var index = 0; + while (EnumDisplaySettings(DeviceName, index, ref dm)) + { + allMode.Add(dm); + index++; + } + + return allMode; + } + + public static void PlayWindowsMedia(string file) + { + var 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 (var mclass = new ManagementClass("WmiMonitorBrightnessMethods")) + { + mclass.Scope = new ManagementScope(@"\\.\root\wmi"); + using (var instances = mclass.GetInstances()) + { + foreach (ManagementObject instance in instances) + { + object[] args = { 1, brightness }; + instance.InvokeMethod("WmiSetBrightness", args); + } + } + } + } + catch + { + } + } + + public static short GetBrightness() + { + try + { + using (var mclass = new ManagementClass("WmiMonitorBrightness")) + { + mclass.Scope = new ManagementScope(@"\\.\root\wmi"); + using (var 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 Display + { + 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 Display lpDevMode, int dwFlags); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref Display lpDevMode); + + [Flags] + public enum DisplayDeviceStateFlags + { + /// The device is part of the desktop. + AttachedToDesktop = 0x1, + MultiDriver = 0x2, + + /// The device is part of the desktop. + PrimaryDevice = 0x4, + + /// Represents a pseudo device used to mirror application drawing for remoting or other purposes. + MirroringDriver = 0x8, + + /// The device is VGA compatible. + VGACompatible = 0x16, + + /// The device is removable; it cannot be the primary display. + Removable = 0x20, + + /// The device has more display modes than its output devices support. + ModesPruned = 0x8000000, + Remote = 0x4000000, + Disconnect = 0x2000000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct DisplayDevice + { + [MarshalAs(UnmanagedType.U4)] public int cb; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string DeviceName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceString; + + [MarshalAs(UnmanagedType.U4)] public DisplayDeviceStateFlags StateFlags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceID; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceKey; + } + + [DllImport("User32.dll")] + private static extern int EnumDisplayDevices(string lpDevice, int iDevNum, ref DisplayDevice lpDisplayDevice, + int dwFlags); + + #endregion + + #region events + + public static event DisplaySettingsChangedEventHandler DisplaySettingsChanged; + public delegate void DisplaySettingsChangedEventHandler(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/PerformanceManager.cs b/HandheldCompanion/Managers/PerformanceManager.cs index c3f327292..f382cd1aa 100644 --- a/HandheldCompanion/Managers/PerformanceManager.cs +++ b/HandheldCompanion/Managers/PerformanceManager.cs @@ -1,3 +1,4 @@ +using HandheldCompanion.ADLX; using HandheldCompanion.Controls; using HandheldCompanion.Misc; using HandheldCompanion.Processors; @@ -76,7 +77,7 @@ public static class PerformanceManager private static readonly double[] FPSHistory = new double[6]; private static bool gfxWatchdogPendingStop; - private static Processor processor = new(); + private static Processor processor; private static double ProcessValueFPSPrevious; private static double StoredGfxClock; @@ -104,9 +105,6 @@ static PerformanceManager() autoWatchdog.Elapsed += AutoTDPWatchdog_Elapsed; // manage events - ProfileManager.Applied += ProfileManager_Applied; - ProfileManager.Discarded += ProfileManager_Discarded; - ProfileManager.Updated += ProfileManager_Updated; PowerProfileManager.Applied += PowerProfileManager_Applied; PowerProfileManager.Discarded += PowerProfileManager_Discarded; PlatformManager.LibreHardwareMonitor.GPUClockChanged += LibreHardwareMonitor_GPUClockChanged; @@ -115,12 +113,6 @@ static PerformanceManager() SettingsManager.SettingValueChanged += SettingsManagerOnSettingValueChanged; HotkeysManager.CommandExecuted += HotkeysManager_CommandExecuted; - // move me - SystemManager.StateChanged_RSR += SystemManager_StateChanged_RSR; - SystemManager.StateChanged_IntegerScaling += SystemManager_StateChanged_IntegerScaling; - SystemManager.StateChanged_ImageSharpening += SystemManager_StateChanged_ImageSharpening; - SystemManager.StateChanged_GPUScaling += SystemManager_StateChanged_GPUScaling; - currentCoreCount = Environment.ProcessorCount; MaxDegreeOfParallelism = Convert.ToInt32(Environment.ProcessorCount / 2); } @@ -178,156 +170,6 @@ private static void HotkeysManager_CommandExecuted(string listener) } } - private static void ProfileManager_Applied(Profile profile, UpdateSource source) - { - try - { - // apply profile GPU Scaling - // apply profile scaling mode - if (profile.GPUScaling) - { - ADLXWrapper.SetGPUScaling(true); - - var scalingMode = profile.ScalingMode; - - // RSR + ScalingMode.Center not supported (stop applying center if it's somehow on a profile) - // Technically shouldn't occur and be stopped from UI, but could potentially be possible from older versions - if (profile.ScalingMode == 2 && profile.RSREnabled) - scalingMode = 1; - - ADLXWrapper.SetScalingMode(scalingMode); - - // apply profile RSR - if (profile.RSREnabled) - { - // mutually exclusive - ADLXWrapper.SetIntegerScaling(false); - ADLXWrapper.SetImageSharpening(false); - - ADLXWrapper.SetRSR(true); - ADLXWrapper.SetRSRSharpness(profile.RSRSharpness); - } - else if (ADLXWrapper.GetRSR()) - { - ADLXWrapper.SetRSR(false); - ADLXWrapper.SetRSRSharpness(20); - } - - // apply profile Integer Scaling - if (profile.IntegerScalingEnabled) - { - // mutually exclusive - ADLXWrapper.SetRSR(false); - - ADLXWrapper.SetIntegerScaling(true); - } - else if (ADLXWrapper.GetIntegerScaling()) - { - ADLXWrapper.SetIntegerScaling(false); - } - } - else if (ADLXWrapper.GetGPUScaling()) - { - ADLXWrapper.SetGPUScaling(false); - } - - // apply profile image sharpening - if (profile.RISEnabled) - { - // mutually exclusive - ADLXWrapper.SetRSR(false); - - ADLXWrapper.SetImageSharpening(profile.RISEnabled); - ADLXWrapper.SetImageSharpeningSharpness(profile.RISSharpness); - } - else if (ADLXWrapper.GetImageSharpening()) - { - ADLXWrapper.SetImageSharpening(false); - } - } - catch { } - } - - private static void ProfileManager_Discarded(Profile profile) - { - try - { - // restore default GPU Scaling - if (profile.GPUScaling && ADLXWrapper.GetGPUScaling()) - { - ADLXWrapper.SetGPUScaling(false); - } - - // restore default RSR - if (profile.RSREnabled && ADLXWrapper.GetRSR()) - { - ADLXWrapper.SetRSR(false); - ADLXWrapper.SetRSRSharpness(20); - } - - // restore default integer scaling - if (profile.IntegerScalingEnabled && ADLXWrapper.GetIntegerScaling()) - { - ADLXWrapper.SetIntegerScaling(false); - } - - // restore default image sharpening - if (profile.RISEnabled && ADLXWrapper.GetImageSharpening()) - { - ADLXWrapper.SetImageSharpening(false); - } - } - catch { } - } - - // todo: moveme - private static void ProfileManager_Updated(Profile profile, UpdateSource source, bool isCurrent) - { - ProcessEx.SetAppCompatFlag(profile.Path, ProcessEx.DisabledMaximizedWindowedValue, !profile.FullScreenOptimization); - ProcessEx.SetAppCompatFlag(profile.Path, ProcessEx.HighDPIAwareValue, !profile.HighDPIAware); - } - - private static void SystemManager_StateChanged_RSR(bool Supported, bool Enabled, int Sharpness) - { - Profile profile = ProfileManager.GetCurrent(); - if (Enabled != profile.RSREnabled) - ADLXWrapper.SetRSR(profile.RSREnabled); - if (Sharpness != profile.RSRSharpness) - ADLXWrapper.SetRSRSharpness(profile.RSRSharpness); - } - - private static void SystemManager_StateChanged_GPUScaling(bool Supported, bool Enabled, int Mode) - { - Profile profile = ProfileManager.GetCurrent(); - if (Enabled != profile.GPUScaling) - ADLXWrapper.SetGPUScaling(profile.GPUScaling); - if (Mode != profile.ScalingMode) - { - // RSR + ScalingMode.Center not supported (stop applying center if it's somehow on a profile) - // Technically shouldn't occur and be stopped from UI, but could potentially be possible from older versions - if (profile.ScalingMode == 2 && profile.RSREnabled) - return; - - ADLXWrapper.SetScalingMode(profile.ScalingMode); - } - } - - private static void SystemManager_StateChanged_IntegerScaling(bool Supported, bool Enabled) - { - Profile profile = ProfileManager.GetCurrent(); - if (Enabled != profile.IntegerScalingEnabled) - ADLXWrapper.SetIntegerScaling(profile.IntegerScalingEnabled); - } - - private static void SystemManager_StateChanged_ImageSharpening(bool Enabled, int Sharpness) - { - Profile profile = ProfileManager.GetCurrent(); - if (Enabled != profile.RISEnabled) - ADLXWrapper.SetImageSharpening(profile.RISEnabled); - if (Sharpness != profile.RISSharpness) - ADLXWrapper.SetImageSharpeningSharpness(Sharpness); - } - private static void PowerProfileManager_Applied(PowerProfile profile, UpdateSource source) { // apply profile defined TDP diff --git a/HandheldCompanion/Managers/PowerManager.cs b/HandheldCompanion/Managers/PowerManager.cs deleted file mode 100644 index 1158860f0..000000000 --- a/HandheldCompanion/Managers/PowerManager.cs +++ /dev/null @@ -1,193 +0,0 @@ -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Windows.Forms; -using SystemPowerManager = Windows.System.Power.PowerManager; - -namespace HandheldCompanion.Managers; - -public static class PowerManager -{ - public enum SystemStatus - { - SystemBooting = 0, - SystemPending = 1, - SystemReady = 2 - } - - private static bool IsPowerSuspended; - private static bool IsSessionLocked = true; - - private static SystemStatus currentSystemStatus = SystemStatus.SystemBooting; - private static SystemStatus previousSystemStatus = SystemStatus.SystemBooting; - - public static bool IsInitialized; - - public static readonly SortedDictionary PowerStatusIcon = new() - { - { "Battery0", "\uE850" }, - { "Battery1", "\uE851" }, - { "Battery2", "\uE852" }, - { "Battery3", "\uE853" }, - { "Battery4", "\uE854" }, - { "Battery5", "\uE855" }, - { "Battery6", "\uE856" }, - { "Battery7", "\uE857" }, - { "Battery8", "\uE858" }, - { "Battery9", "\uE859" }, - { "Battery10", "\uE83F" }, - - { "BatteryCharging0", "\uE85A" }, - { "BatteryCharging1", "\uE85B" }, - { "BatteryCharging2", "\uE85C" }, - { "BatteryCharging3", "\uE85D" }, - { "BatteryCharging4", "\uE85E" }, - { "BatteryCharging5", "\uE85F" }, - { "BatteryCharging6", "\uE860" }, - { "BatteryCharging7", "\uE861" }, - { "BatteryCharging8", "\uE862" }, - { "BatteryCharging9", "\uE83E" }, - { "BatteryCharging10", "\uEA93" }, - - { "BatterySaver0", "\uE863" }, - { "BatterySaver1", "\uE864" }, - { "BatterySaver2", "\uE865" }, - { "BatterySaver3", "\uE866" }, - { "BatterySaver4", "\uE867" }, - { "BatterySaver5", "\uE868" }, - { "BatterySaver6", "\uE869" }, - { "BatterySaver7", "\uE86A" }, - { "BatterySaver8", "\uE86B" }, - { "BatterySaver9", "\uEA94" }, - { "BatterySaver10", "\uEA95" } - }; - - static PowerManager() - { - // listen to system events - SystemEvents.PowerModeChanged += OnPowerChange; - SystemEvents.SessionSwitch += OnSessionSwitch; - - SystemPowerManager.BatteryStatusChanged += BatteryStatusChanged; - SystemPowerManager.EnergySaverStatusChanged += BatteryStatusChanged; - SystemPowerManager.PowerSupplyStatusChanged += BatteryStatusChanged; - SystemPowerManager.RemainingChargePercentChanged += BatteryStatusChanged; - SystemPowerManager.RemainingDischargeTimeChanged += BatteryStatusChanged; - } - - #region import - - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess); - - #endregion - - private static void BatteryStatusChanged(object sender, object e) - { - PowerStatusChanged?.Invoke(SystemInformation.PowerStatus); - } - - public static void Start(bool service = false) - { - // check if current session is locked - var handle = OpenInputDesktop(0, false, 0); - IsSessionLocked = handle == IntPtr.Zero; - - SystemRoutine(); - - IsInitialized = true; - Initialized?.Invoke(); - - PowerStatusChanged?.Invoke(SystemInformation.PowerStatus); - - LogManager.LogInformation("{0} has started", "PowerManager"); - } - - public static void Stop() - { - if (!IsInitialized) - return; - - IsInitialized = false; - - // stop listening to system events - SystemEvents.PowerModeChanged -= OnPowerChange; - SystemEvents.SessionSwitch -= OnSessionSwitch; - - LogManager.LogInformation("{0} has stopped", "PowerManager"); - } - - private static void OnPowerChange(object s, PowerModeChangedEventArgs e) - { - switch (e.Mode) - { - case PowerModes.Resume: - IsPowerSuspended = false; - break; - case PowerModes.Suspend: - IsPowerSuspended = true; - break; - default: - case PowerModes.StatusChange: - PowerStatusChanged?.Invoke(SystemInformation.PowerStatus); - return; - } - - LogManager.LogDebug("Device power mode set to {0}", e.Mode); - - SystemRoutine(); - } - - private static void OnSessionSwitch(object sender, SessionSwitchEventArgs e) - { - switch (e.Reason) - { - case SessionSwitchReason.SessionUnlock: - IsSessionLocked = false; - break; - case SessionSwitchReason.SessionLock: - IsSessionLocked = true; - break; - default: - return; - } - - LogManager.LogDebug("Session switched to {0}", e.Reason); - - SystemRoutine(); - } - - private static void SystemRoutine() - { - if (!IsPowerSuspended && !IsSessionLocked) - currentSystemStatus = SystemStatus.SystemReady; - else - currentSystemStatus = SystemStatus.SystemPending; - - // only raise event is system status has changed - if (previousSystemStatus != currentSystemStatus) - { - LogManager.LogInformation("System status set to {0}", currentSystemStatus); - SystemStatusChanged?.Invoke(currentSystemStatus, previousSystemStatus); - - previousSystemStatus = currentSystemStatus; - } - } - - #region events - - public static event SystemStatusChangedEventHandler SystemStatusChanged; - - public delegate void SystemStatusChangedEventHandler(SystemStatus status, SystemStatus prevStatus); - - public static event PowerStatusChangedEventHandler PowerStatusChanged; - - public delegate void PowerStatusChangedEventHandler(PowerStatus status); - - public static event InitializedEventHandler Initialized; - - public delegate void InitializedEventHandler(); - - #endregion -} \ No newline at end of file diff --git a/HandheldCompanion/Managers/ProcessManager.cs b/HandheldCompanion/Managers/ProcessManager.cs index 048b5a331..6c6924d5c 100644 --- a/HandheldCompanion/Managers/ProcessManager.cs +++ b/HandheldCompanion/Managers/ProcessManager.cs @@ -280,6 +280,7 @@ private static bool CreateProcess(int ProcessID, int NativeWindowHandle = 0, boo processEx.MainWindowHandle = hWnd; processEx.MainThread = GetMainThread(proc); + processEx.MainThread.Disposed += (sender, e) => processEx.MainThreadDisposed(); processEx.Platform = PlatformManager.GetPlatform(proc); Processes.TryAdd(ProcessID, processEx); @@ -377,7 +378,7 @@ private static ProcessFilter GetFilter(string exec, string path, string MainWind } } - private static ProcessThread GetMainThread(Process process) + public static ProcessThread GetMainThread(Process process) { ProcessThread mainThread = null; var startTime = DateTime.MaxValue; diff --git a/HandheldCompanion/Managers/SettingsManager.cs b/HandheldCompanion/Managers/SettingsManager.cs index 114df2cd2..63234e027 100644 --- a/HandheldCompanion/Managers/SettingsManager.cs +++ b/HandheldCompanion/Managers/SettingsManager.cs @@ -176,10 +176,10 @@ private static object GetProperty(string name, bool temporary = false) } case "HasBrightnessSupport": - return SystemManager.HasBrightnessSupport(); + return MultimediaManager.HasBrightnessSupport(); case "HasVolumeSupport": - return SystemManager.HasVolumeSupport(); + return MultimediaManager.HasVolumeSupport(); default: { diff --git a/HandheldCompanion/Managers/SystemManager.cs b/HandheldCompanion/Managers/SystemManager.cs index f1c91639a..a38714a9b 100644 --- a/HandheldCompanion/Managers/SystemManager.cs +++ b/HandheldCompanion/Managers/SystemManager.cs @@ -1,396 +1,107 @@ -using HandheldCompanion.Managers.Desktop; -using HandheldCompanion.Misc; -using Microsoft.Win32; -using NAudio.CoreAudioApi; -using NAudio.CoreAudioApi.Interfaces; +using Microsoft.Win32; using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Management; -using System.Media; using System.Runtime.InteropServices; -using System.Threading; -using System.Timers; using System.Windows.Forms; -using Timer = System.Timers.Timer; +using SystemPowerManager = Windows.System.Power.PowerManager; namespace HandheldCompanion.Managers; public static class SystemManager { - 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; - - private const int ADLXUpdateInterval = 2000; - private static readonly Timer ADLXTimer; - - private static bool prevRSRSupport = false; - private static bool prevRSR = false; - private static int prevRSRSharpness = -1; - - private static bool prevGPUScalingSupport = false; - private static bool prevGPUScaling = false; - private static int prevScalingMode = -1; - - private static bool prevIntegerScaling = false; - private static bool prevIntegerScalingSupport = false; - - private static bool prevImageSharpening = false; - private static int prevImageSharpeningSharpness = -1; - - public static bool IsInitialized; - - static SystemManager() - { - // ADLX - ADLXTimer = new Timer(ADLXUpdateInterval); - ADLXTimer.AutoReset = true; - ADLXTimer.Elapsed += ADLXTimer_Elapsed; - - // 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; - } - - // create a lock object - private static object ADLXlockObject = new object(); - private static void ADLXTimer_Elapsed(object? sender, ElapsedEventArgs e) - { - if (Monitor.TryEnter(ADLXlockObject)) - { - bool GPUScaling = false; - - try - { - // check for GPU Scaling support - // if yes, get GPU Scaling (bool) - bool GPUScalingSupport = ADLXWrapper.HasGPUScalingSupport(); - if (GPUScalingSupport) - GPUScaling = ADLXWrapper.GetGPUScaling(); - - // check for Scaling Mode support - // if yes, get Scaling Mode (int) - bool ScalingSupport = ADLXWrapper.HasScalingModeSupport(); - int ScalingMode = 0; - if (ScalingSupport) - ScalingMode = ADLXWrapper.GetScalingMode(); - - if ((GPUScalingSupport != prevGPUScalingSupport) || (GPUScaling != prevGPUScaling) || (ScalingMode != prevScalingMode)) - { - // raise event - StateChanged_GPUScaling?.Invoke(GPUScalingSupport, GPUScaling, ScalingMode); - - prevGPUScaling = GPUScaling; - prevScalingMode = ScalingMode; - prevGPUScalingSupport = GPUScalingSupport; - } - } - catch { } - - try - { - // get rsr - bool RSRSupport = false; - bool RSR = false; - int RSRSharpness = ADLXWrapper.GetRSRSharpness(); - - DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); - while (DateTime.Now < timeout && !RSRSupport) - { - RSRSupport = ADLXWrapper.HasRSRSupport(); - Thread.Sleep(250); - } - RSR = ADLXWrapper.GetRSR(); - - if ((RSRSupport != prevRSRSupport) || (RSR != prevRSR) || (RSRSharpness != prevRSRSharpness)) - { - // raise event - StateChanged_RSR?.Invoke(RSRSupport, RSR, RSRSharpness); - - prevRSRSupport = RSRSupport; - prevRSR = RSR; - prevRSRSharpness = RSRSharpness; - } - } - catch { } - - try - { - // get gpu scaling and scaling mode - bool IntegerScalingSupport = false; - bool IntegerScaling = false; - - DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(3)); - while (DateTime.Now < timeout && !IntegerScalingSupport) - { - IntegerScalingSupport = ADLXWrapper.HasIntegerScalingSupport(); - Thread.Sleep(250); - } - IntegerScaling = ADLXWrapper.GetIntegerScaling(); - - if ((IntegerScalingSupport != prevIntegerScalingSupport) || (IntegerScaling != prevIntegerScaling)) - { - // raise event - StateChanged_IntegerScaling?.Invoke(IntegerScalingSupport, IntegerScaling); - - prevIntegerScalingSupport = IntegerScalingSupport; - prevIntegerScaling = IntegerScaling; - } - } - catch { } - - try - { - bool ImageSharpening = ADLXWrapper.GetImageSharpening(); - int ImageSharpeningSharpness = ADLXWrapper.GetImageSharpeningSharpness(); - - if ((ImageSharpening != prevImageSharpening) || (ImageSharpeningSharpness != prevImageSharpeningSharpness)) - { - // raise event - StateChanged_ImageSharpening?.Invoke(ImageSharpening, ImageSharpeningSharpness); - - prevImageSharpening = ImageSharpening; - prevImageSharpeningSharpness = ImageSharpeningSharpness; - } - } - catch { } - - Monitor.Exit(ADLXlockObject); - } - } - - private static void AudioEndpointVolume_OnVolumeNotification(AudioVolumeNotificationData data) - { - VolumeNotification?.Invoke(data.MasterVolume * 100.0f); - } - - private static void SetDefaultAudioEndPoint() + public enum SystemStatus { - 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"); - } + SystemBooting = 0, + SystemPending = 1, + SystemReady = 2 } - private static void SettingsManager_SettingValueChanged(string name, object value) - { - switch (name) - { - case "NativeDisplayOrientation": - { - var nativeOrientation = (ScreenRotation.Rotations)Convert.ToInt32(value); + private static bool IsPowerSuspended; + private static bool IsSessionLocked = true; - if (!IsInitialized) - return; + private static SystemStatus currentSystemStatus = SystemStatus.SystemBooting; + private static SystemStatus previousSystemStatus = SystemStatus.SystemBooting; - var oldOrientation = screenOrientation.rotation; - screenOrientation = new ScreenRotation(screenOrientation.rotationUnnormalized, nativeOrientation); + public static bool IsInitialized; - if (oldOrientation != screenOrientation.rotation) - // Though the real orientation didn't change, raise event because the interpretation of it changed - DisplayOrientationChanged?.Invoke(screenOrientation); - } - break; - } - } + public static readonly SortedDictionary PowerStatusIcon = new() + { + { "Battery0", "\uE850" }, + { "Battery1", "\uE851" }, + { "Battery2", "\uE852" }, + { "Battery3", "\uE853" }, + { "Battery4", "\uE854" }, + { "Battery5", "\uE855" }, + { "Battery6", "\uE856" }, + { "Battery7", "\uE857" }, + { "Battery8", "\uE858" }, + { "Battery9", "\uE859" }, + { "Battery10", "\uE83F" }, + + { "BatteryCharging0", "\uE85A" }, + { "BatteryCharging1", "\uE85B" }, + { "BatteryCharging2", "\uE85C" }, + { "BatteryCharging3", "\uE85D" }, + { "BatteryCharging4", "\uE85E" }, + { "BatteryCharging5", "\uE85F" }, + { "BatteryCharging6", "\uE860" }, + { "BatteryCharging7", "\uE861" }, + { "BatteryCharging8", "\uE862" }, + { "BatteryCharging9", "\uE83E" }, + { "BatteryCharging10", "\uEA93" }, + + { "BatterySaver0", "\uE863" }, + { "BatterySaver1", "\uE864" }, + { "BatterySaver2", "\uE865" }, + { "BatterySaver3", "\uE866" }, + { "BatterySaver4", "\uE867" }, + { "BatterySaver5", "\uE868" }, + { "BatterySaver6", "\uE869" }, + { "BatterySaver7", "\uE86A" }, + { "BatterySaver8", "\uE86B" }, + { "BatterySaver9", "\uEA94" }, + { "BatterySaver10", "\uEA95" } + }; - private static void HotkeysManager_CommandExecuted(string listener) + static SystemManager() { - switch (listener) - { - case "increaseBrightness": - { - var stepRoundDn = (int)Math.Floor(GetBrightness() / 5.0d); - var brightness = stepRoundDn * 5 + 5; - SetBrightness(brightness); - } - break; - case "decreaseBrightness": - { - var stepRoundUp = (int)Math.Ceiling(GetBrightness() / 5.0d); - var brightness = stepRoundUp * 5 - 5; - SetBrightness(brightness); - } - break; - case "increaseVolume": - { - var stepRoundDn = (int)Math.Floor(Math.Round(GetVolume() / 5.0d, 2)); - var volume = stepRoundDn * 5 + 5; - SetVolume(volume); - } - break; - case "decreaseVolume": - { - var stepRoundUp = (int)Math.Ceiling(Math.Round(GetVolume() / 5.0d, 2)); - var volume = stepRoundUp * 5 - 5; - SetVolume(volume); - } - break; - } - } + // listen to system events + SystemEvents.PowerModeChanged += OnPowerChange; + SystemEvents.SessionSwitch += OnSessionSwitch; - private static void onWMIEvent(object sender, EventArrivedEventArgs e) - { - var brightness = Convert.ToInt32(e.NewEvent.Properties["Brightness"].Value); - BrightnessNotification?.Invoke(brightness); + SystemPowerManager.BatteryStatusChanged += BatteryStatusChanged; + SystemPowerManager.EnergySaverStatusChanged += BatteryStatusChanged; + SystemPowerManager.PowerSupplyStatusChanged += BatteryStatusChanged; + SystemPowerManager.RemainingChargePercentChanged += BatteryStatusChanged; + SystemPowerManager.RemainingDischargeTimeChanged += BatteryStatusChanged; } - private static void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) - { - Screen PrimaryScreen = Screen.PrimaryScreen; + #region import - if (desktopScreen is null || desktopScreen.PrimaryScreen.DeviceName != PrimaryScreen.DeviceName) - { - // update current desktop screen - desktopScreen = new DesktopScreen(PrimaryScreen); - desktopScreen.devMode = GetDisplay(desktopScreen.PrimaryScreen.DeviceName); + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess); - // pull resolutions details - List resolutions = GetResolutions(desktopScreen.PrimaryScreen.DeviceName); - foreach (Display 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) - { - var 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(screenResolution); - - // raise event - if (oldOrientation != screenOrientation.rotation) - DisplayOrientationChanged?.Invoke(screenOrientation); - } - - public static DesktopScreen GetDesktopScreen() - { - return desktopScreen; - } + #endregion - public static ScreenRotation GetScreenOrientation() + private static void BatteryStatusChanged(object sender, object e) { - return screenOrientation; + PowerStatusChanged?.Invoke(SystemInformation.PowerStatus); } public static void Start() { - // start brightness watcher - BrightnessWatcher.Start(); - ADLXTimer.Start(); - - // force trigger events - SystemEvents_DisplaySettingsChanged(null, null); + // check if current session is locked + var handle = OpenInputDesktop(0, false, 0); + IsSessionLocked = handle == IntPtr.Zero; - // get native resolution - ScreenResolution nativeResolution = desktopScreen.screenResolutions.First(); - - // get integer scaling dividers - int idx = 1; - - while (true) - { - int height = nativeResolution.Height / idx; - var dividedRes = desktopScreen.screenResolutions.FirstOrDefault(r => r.Height == height); - if (dividedRes is null) - break; - - desktopScreen.screenDividers.Add(new(idx, dividedRes)); - idx++; - } + SystemRoutine(); IsInitialized = true; Initialized?.Invoke(); - LogManager.LogInformation("{0} has started", "SystemManager"); + PowerStatusChanged?.Invoke(SystemInformation.PowerStatus); + + LogManager.LogInformation("{0} has started", "PowerManager"); } public static void Stop() @@ -398,349 +109,84 @@ public static void Stop() if (!IsInitialized) return; - // stop brightness watcher - BrightnessWatcher.Stop(); - ADLXTimer.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; - - var ret = false; - long RetVal = 0; - var dm = new Display(); - dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); - dm.dmPelsWidth = width; - dm.dmPelsHeight = height; - dm.dmDisplayFrequency = displayFrequency; - dm.dmFields = Display.DM_PELSWIDTH | Display.DM_PELSHEIGHT | Display.DM_DISPLAYFREQUENCY; - RetVal = ChangeDisplaySettings(ref dm, CDS_TEST); - if (RetVal == 0) - { - RetVal = ChangeDisplaySettings(ref dm, 0); - ret = true; - } + // stop listening to system events + SystemEvents.PowerModeChanged -= OnPowerChange; + SystemEvents.SessionSwitch -= OnSessionSwitch; - return ret; + LogManager.LogInformation("{0} has stopped", "PowerManager"); } - public static bool SetResolution(int width, int height, int displayFrequency, int bitsPerPel) + private static void OnPowerChange(object s, PowerModeChangedEventArgs e) { - if (!IsInitialized) - return false; - - var ret = false; - long RetVal = 0; - var dm = new Display(); - dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); - dm.dmPelsWidth = width; - dm.dmPelsHeight = height; - dm.dmDisplayFrequency = displayFrequency; - dm.dmBitsPerPel = bitsPerPel; - dm.dmFields = Display.DM_PELSWIDTH | Display.DM_PELSHEIGHT | Display.DM_DISPLAYFREQUENCY; - RetVal = ChangeDisplaySettings(ref dm, CDS_TEST); - if (RetVal == 0) + switch (e.Mode) { - RetVal = ChangeDisplaySettings(ref dm, 0); - ret = true; - } - - return ret; - } - - public static Display GetDisplay(string DeviceName) - { - var dm = new Display(); - dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); - bool mybool; - mybool = EnumDisplaySettings(DeviceName, -1, ref dm); - return dm; - } - - public static List GetResolutions(string DeviceName) - { - var allMode = new List(); - var dm = new Display(); - dm.dmSize = (short)Marshal.SizeOf(typeof(Display)); - var index = 0; - while (EnumDisplaySettings(DeviceName, index, ref dm)) - { - allMode.Add(dm); - index++; + case PowerModes.Resume: + IsPowerSuspended = false; + break; + case PowerModes.Suspend: + IsPowerSuspended = true; + break; + default: + case PowerModes.StatusChange: + PowerStatusChanged?.Invoke(SystemInformation.PowerStatus); + return; } - return allMode; - } - - public static void PlayWindowsMedia(string file) - { - var path = Path.Combine(@"c:\Windows\Media\", file); - if (File.Exists(path)) - new SoundPlayer(path).Play(); - } + LogManager.LogDebug("Device power mode set to {0}", e.Mode); - public static bool HasVolumeSupport() - { - return VolumeSupport; + SystemRoutine(); } - public static void SetVolume(double volume) + private static void OnSessionSwitch(object sender, SessionSwitchEventArgs e) { - 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 (var mclass = new ManagementClass("WmiMonitorBrightnessMethods")) - { - mclass.Scope = new ManagementScope(@"\\.\root\wmi"); - using (var instances = mclass.GetInstances()) - { - foreach (ManagementObject instance in instances) - { - object[] args = { 1, brightness }; - instance.InvokeMethod("WmiSetBrightness", args); - } - } - } - } - catch + switch (e.Reason) { + case SessionSwitchReason.SessionUnlock: + IsSessionLocked = false; + break; + case SessionSwitchReason.SessionLock: + IsSessionLocked = true; + break; + default: + return; } - } - public static short GetBrightness() - { - try - { - using (var mclass = new ManagementClass("WmiMonitorBrightness")) - { - mclass.Scope = new ManagementScope(@"\\.\root\wmi"); - using (var instances = mclass.GetInstances()) - { - foreach (ManagementObject instance in instances) - return (byte)instance.GetPropertyValue("CurrentBrightness"); - } - } - - return 0; - } - catch - { - } + LogManager.LogDebug("Session switched to {0}", e.Reason); - return -1; + SystemRoutine(); } - private class MMDeviceNotificationClient : IMMNotificationClient + private static void SystemRoutine() { - 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) - { - } + if (!IsPowerSuspended && !IsSessionLocked) + currentSystemStatus = SystemStatus.SystemReady; + else + currentSystemStatus = SystemStatus.SystemPending; - public void OnPropertyValueChanged(string deviceId, PropertyKey key) + // only raise event is system status has changed + if (previousSystemStatus != currentSystemStatus) { - } - } - - #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; + LogManager.LogInformation("System status set to {0}", currentSystemStatus); + SystemStatusChanged?.Invoke(currentSystemStatus, previousSystemStatus); - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct Display - { - 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}"; + previousSystemStatus = currentSystemStatus; } } - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern int ChangeDisplaySettings([In] ref Display lpDevMode, int dwFlags); - - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref Display lpDevMode); - - [Flags] - public enum DisplayDeviceStateFlags - { - /// The device is part of the desktop. - AttachedToDesktop = 0x1, - MultiDriver = 0x2, - - /// The device is part of the desktop. - PrimaryDevice = 0x4, - - /// Represents a pseudo device used to mirror application drawing for remoting or other purposes. - MirroringDriver = 0x8, - - /// The device is VGA compatible. - VGACompatible = 0x16, - - /// The device is removable; it cannot be the primary display. - Removable = 0x20, - - /// The device has more display modes than its output devices support. - ModesPruned = 0x8000000, - Remote = 0x4000000, - Disconnect = 0x2000000 - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct DisplayDevice - { - [MarshalAs(UnmanagedType.U4)] public int cb; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] - public string DeviceName; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string DeviceString; - - [MarshalAs(UnmanagedType.U4)] public DisplayDeviceStateFlags StateFlags; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string DeviceID; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] - public string DeviceKey; - } - - [DllImport("User32.dll")] - private static extern int EnumDisplayDevices(string lpDevice, int iDevNum, ref DisplayDevice lpDisplayDevice, - int dwFlags); - - #endregion - #region events - public static event RSRStateChangedEventHandler StateChanged_RSR; - public delegate void RSRStateChangedEventHandler(bool Supported, bool Enabled, int Sharpness); - - public static event IntegerScalingStateChangedEventHandler StateChanged_IntegerScaling; - public delegate void IntegerScalingStateChangedEventHandler(bool Supported, bool Enabled); + public static event SystemStatusChangedEventHandler SystemStatusChanged; - public static event ImageSharpeningISStateChangedEventHandler StateChanged_ImageSharpening; - public delegate void ImageSharpeningISStateChangedEventHandler(bool Enabled, int Sharpness); + public delegate void SystemStatusChangedEventHandler(SystemStatus status, SystemStatus prevStatus); - public static event GPUScalingStateChangedEventHandler StateChanged_GPUScaling; - public delegate void GPUScalingStateChangedEventHandler(bool Supported, bool Enabled, int Mode); + public static event PowerStatusChangedEventHandler PowerStatusChanged; - public static event DisplaySettingsChangedEventHandler DisplaySettingsChanged; - public delegate void DisplaySettingsChangedEventHandler(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 delegate void PowerStatusChangedEventHandler(PowerStatus status); public static event InitializedEventHandler Initialized; + public delegate void InitializedEventHandler(); #endregion diff --git a/HandheldCompanion/Misc/ADLXWrapper.cs b/HandheldCompanion/Misc/ADLXWrapper.cs deleted file mode 100644 index 949c47684..000000000 --- a/HandheldCompanion/Misc/ADLXWrapper.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace HandheldCompanion.Misc -{ - public static class ADLXWrapper - { - private static object ADLXLock = new(); - - private static T Execute(Func func, T defaultValue) - { - lock (ADLXLock) - { - Task task = Task.Run(func); - if (task.Wait(TimeSpan.FromSeconds(5))) - { - return task.Result; - } - - return defaultValue; - } - } - - // RSR - public static bool HasRSRSupport() => Execute(ADLXBackend.HasRSRSupport, false); - public static bool GetRSR() => Execute(ADLXBackend.GetRSR, false); - public static bool SetRSR(bool enable) => Execute(() => ADLXBackend.SetRSR(enable), false); - public static int GetRSRSharpness() => Execute(ADLXBackend.GetRSRSharpness, -1); - public static bool SetRSRSharpness(int sharpness) => Execute(() => ADLXBackend.SetRSRSharpness(sharpness), false); - - // ImageSharpening - public static bool GetImageSharpening() => Execute(() => ADLXBackend.GetImageSharpening(0), false); - public static bool SetImageSharpening(bool enable) => Execute(() => ADLXBackend.SetImageSharpening(0, enable), false); - public static int GetImageSharpeningSharpness() => Execute(() => ADLXBackend.GetImageSharpeningSharpness(0), -1); - public static bool SetImageSharpeningSharpness(int sharpness) => Execute(() => ADLXBackend.SetImageSharpeningSharpness(0, sharpness), false); - - // IntegerScaling - public static bool HasIntegerScalingSupport() => Execute(() => ADLXBackend.HasIntegerScalingSupport(0), false); - public static bool GetIntegerScaling() => Execute(() => ADLXBackend.GetIntegerScaling(0), false); - public static bool SetIntegerScaling(bool enabled) => Execute(() => ADLXBackend.SetIntegerScaling(0, enabled), false); - - public static bool HasGPUScalingSupport() => Execute(() => ADLXBackend.HasGPUScalingSupport(0), false); - public static bool GetGPUScaling() => Execute(() => ADLXBackend.GetGPUScaling(0), false); - public static bool SetGPUScaling(bool enabled) => Execute(() => ADLXBackend.SetGPUScaling(0, enabled), false); - - public static bool HasScalingModeSupport() => Execute(() => ADLXBackend.HasScalingModeSupport(0), false); - public static int GetScalingMode() => Execute(() => ADLXBackend.GetScalingMode(0), -1); - public static bool SetScalingMode(int mode) => Execute(() => ADLXBackend.SetScalingMode(0, mode), false); - } -} diff --git a/HandheldCompanion/Misc/MotherboardInfo.cs b/HandheldCompanion/Misc/MotherboardInfo.cs index 6d4539275..bfde72ce0 100644 --- a/HandheldCompanion/Misc/MotherboardInfo.cs +++ b/HandheldCompanion/Misc/MotherboardInfo.cs @@ -1,6 +1,8 @@ using HandheldCompanion.Views; +using System; using System.Collections.Generic; using System.Management; +using static Microsoft.WindowsAPICodePack.Shell.PropertySystem.SystemProperties.System; namespace HandheldCompanion; @@ -18,6 +20,9 @@ public static class MotherboardInfo 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; + public static void UpdateMotherboard() { // slow task, don't call me more than once @@ -25,6 +30,25 @@ public static void UpdateMotherboard() motherboardCollection = motherboardSearcher.Get(); processorCollection = processorSearcher.Get(); displayCollection = displaySearcher.Get(); + videoControllerCollection = videoControllerSearcher.Get(); + } + + public static string VideoController + { + get + { + string manufacturer = string.Empty; + foreach (ManagementObject videoController in videoControllerCollection) + { + manufacturer = videoController["AdapterCompatibility"].ToString(); + if (manufacturer.Contains("Intel")) + break; + else if (manufacturer.Contains("AMD")) + break; + } + + return manufacturer; + } } public static string Availability diff --git a/HandheldCompanion/Misc/Profile.cs b/HandheldCompanion/Misc/Profile.cs index ad5c3e804..3281f5798 100644 --- a/HandheldCompanion/Misc/Profile.cs +++ b/HandheldCompanion/Misc/Profile.cs @@ -93,6 +93,7 @@ public partial class Profile : ICloneable, IComparable public int RSRSharpness { get; set; } = 20; // default AMD value public bool IntegerScalingEnabled { get; set; } = false; public int IntegerScalingDivider { get; set; } = 1; + public byte IntegerScalingType { get; set; } = 0; public bool RISEnabled { get; set; } = false; public int RISSharpness { get; set; } = 80; // default AMD value public bool CPUCoreEnabled { get; set; } = false; diff --git a/HandheldCompanion/Platforms/RTSS.cs b/HandheldCompanion/Platforms/RTSS.cs index d3e9265f3..6ee9719bd 100644 --- a/HandheldCompanion/Platforms/RTSS.cs +++ b/HandheldCompanion/Platforms/RTSS.cs @@ -130,7 +130,7 @@ private void ProfileManager_Applied(Profile profile, UpdateSource source) { int frameLimit = 0; - DesktopScreen desktopScreen = SystemManager.GetDesktopScreen(); + DesktopScreen desktopScreen = MultimediaManager.GetDesktopScreen(); if (desktopScreen is not null) { diff --git a/HandheldCompanion/Processors/Processor.cs b/HandheldCompanion/Processors/Processor.cs index c5ee16530..0916d98d4 100644 --- a/HandheldCompanion/Processors/Processor.cs +++ b/HandheldCompanion/Processors/Processor.cs @@ -34,16 +34,13 @@ public class Processor protected Dictionary m_Values = new(); - protected string Name, ProcessorID; + protected static string Name, ProcessorID; - public Processor() + static Processor() { Name = MotherboardInfo.ProcessorName; ProcessorID = MotherboardInfo.ProcessorID; Manufacturer = MotherboardInfo.ProcessorManufacturer; - - // write default miscs - m_Misc["gfx_clk"] = m_PrevMisc["gfx_clk"] = 0; } public static Processor GetCurrent() @@ -60,6 +57,8 @@ public static Processor GetCurrent() processor = new AMDProcessor(); break; } + // write default miscs + processor.m_Misc["gfx_clk"] = processor.m_PrevMisc["gfx_clk"] = 0; return processor; } diff --git a/HandheldCompanion/Properties/Resources.Designer.cs b/HandheldCompanion/Properties/Resources.Designer.cs index ed38d8465..e3d9db158 100644 --- a/HandheldCompanion/Properties/Resources.Designer.cs +++ b/HandheldCompanion/Properties/Resources.Designer.cs @@ -5743,7 +5743,7 @@ public static string ProfilesPage_GyroSteeringAxisDesc { } /// - /// Looks up a localized string similar to Radeon Image Sharpening. + /// Looks up a localized string similar to Image Sharpening. /// public static string ProfilesPage_ImageSharpening { get { @@ -5994,6 +5994,42 @@ public static string ProfilesPage_ResolutionScaleDesc { } } + /// + /// Looks up a localized string similar to Scaling type. + /// + public static string ProfilesPage_ResolutionScaleType { + get { + return ResourceManager.GetString("ProfilesPage_ResolutionScaleType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select scaling type to adapt the scaling aspect ratio. + /// + public static string ProfilesPage_ResolutionScaleTypeDesc { + get { + return ResourceManager.GetString("ProfilesPage_ResolutionScaleTypeDesc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Integer Scaling. + /// + public static string ProfilesPage_ResolutionScaleTypeInteger { + get { + return ResourceManager.GetString("ProfilesPage_ResolutionScaleTypeInteger", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Nearest Neighbor. + /// + public static string ProfilesPage_ResolutionScaleTypeNearest { + get { + return ResourceManager.GetString("ProfilesPage_ResolutionScaleTypeNearest", resourceCulture); + } + } + /// /// Looks up a localized string similar to Roll. /// diff --git a/HandheldCompanion/Properties/Resources.resx b/HandheldCompanion/Properties/Resources.resx index 25434cf4c..2e9a82158 100644 --- a/HandheldCompanion/Properties/Resources.resx +++ b/HandheldCompanion/Properties/Resources.resx @@ -2767,7 +2767,7 @@ Motion input toggle: press selected button(s) to switch motion state. GPU will scale up lower resolutions to fit the display - Radeon Image Sharpening + Image Sharpening Enhances visual detail @@ -2811,4 +2811,16 @@ Motion input toggle: press selected button(s) to switch motion state. 1/1 only supports Exclusive Fullscreen. + + Scaling type + + + Select scaling type to adapt the scaling aspect ratio + + + Integer Scaling + + + Nearest Neighbor + \ No newline at end of file diff --git a/HandheldCompanion/Utils/ScopedLock.cs b/HandheldCompanion/Utils/ScopedLock.cs index 91fe5ebca..18f158f8a 100644 --- a/HandheldCompanion/Utils/ScopedLock.cs +++ b/HandheldCompanion/Utils/ScopedLock.cs @@ -20,9 +20,6 @@ public class ScopedLock : IDisposable public ScopedLock(LockObject lockVariable) { - // not re-entrant, at least for now - Debug.Assert(lockVariable == false); - lockRef = lockVariable; lockVariable.locked = true; } diff --git a/HandheldCompanion/Views/Pages/PerformancePage.xaml.cs b/HandheldCompanion/Views/Pages/PerformancePage.xaml.cs index eb476e789..775ccdba7 100644 --- a/HandheldCompanion/Views/Pages/PerformancePage.xaml.cs +++ b/HandheldCompanion/Views/Pages/PerformancePage.xaml.cs @@ -56,7 +56,7 @@ public PerformancePage(string? Tag) : this() PerformanceManager.ProcessorStatusChanged += PerformanceManager_StatusChanged; PerformanceManager.EPPChanged += PerformanceManager_EPPChanged; PerformanceManager.Initialized += PerformanceManager_Initialized; - SystemManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; + MultimediaManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; // device settings GPUSlider.Minimum = MainWindow.CurrentDevice.GfxClock[0]; diff --git a/HandheldCompanion/Views/Pages/ProfilesPage.xaml b/HandheldCompanion/Views/Pages/ProfilesPage.xaml index a7ac3ca91..0d2f5ae58 100644 --- a/HandheldCompanion/Views/Pages/ProfilesPage.xaml +++ b/HandheldCompanion/Views/Pages/ProfilesPage.xaml @@ -483,7 +483,12 @@ BorderThickness="0,1,0,0" /> - + @@ -550,7 +555,8 @@ + BorderThickness="0,1,0,0" + Visibility="{Binding ElementName=StackProfileRSR, Path=Visibility}" /> diff --git a/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs b/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs index 7636336d3..69c40660c 100644 --- a/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs +++ b/HandheldCompanion/Views/Pages/ProfilesPage.xaml.cs @@ -1,5 +1,6 @@ using HandheldCompanion.Actions; using HandheldCompanion.Controls; +using HandheldCompanion.GraphicsProcessingUnit; using HandheldCompanion.Inputs; using HandheldCompanion.Managers; using HandheldCompanion.Managers.Desktop; @@ -9,7 +10,6 @@ using HandheldCompanion.Views.Pages.Profiles; using iNKORE.UI.WPF.Modern.Controls; using Microsoft.Win32; -using SharpDX.Win32; using System; using System.Collections.Generic; using System.ComponentModel; @@ -59,12 +59,10 @@ public ProfilesPage() PowerProfileManager.Updated += PowerProfileManager_Updated; PowerProfileManager.Deleted += PowerProfileManager_Deleted; SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; - SystemManager.Initialized += SystemManager_Initialized; - SystemManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; - SystemManager.StateChanged_RSR += SystemManager_StateChanged_RSR; - SystemManager.StateChanged_IntegerScaling += SystemManager_StateChanged_IntegerScaling; - SystemManager.StateChanged_GPUScaling += SystemManager_StateChanged_GPUScaling; + MultimediaManager.Initialized += MultimediaManager_Initialized; + MultimediaManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; PlatformManager.RTSS.Updated += RTSS_Updated; + GPUManager.Initialized += GPUManager_Initialized; UpdateTimer = new Timer(UpdateInterval); UpdateTimer.AutoReset = false; @@ -77,28 +75,47 @@ public ProfilesPage() RTSS_Updated(PlatformManager.RTSS.Status); } - private void SystemManager_Initialized() + private void MultimediaManager_Initialized() { - bool HasScalingModeSupport = ADLXWrapper.HasScalingModeSupport(); - bool HasIntegerScalingSupport = ADLXWrapper.HasIntegerScalingSupport(); - bool HasRSRSupport = ADLXWrapper.HasRSRSupport(); - bool HasGPUScalingSupport = ADLXWrapper.HasGPUScalingSupport(); - bool IsGPUScalingEnabled = ADLXWrapper.GetGPUScaling(); + // UI thread (async) + Application.Current.Dispatcher.BeginInvoke(() => + { + DesktopScreen desktopScreen = MultimediaManager.GetDesktopScreen(); + desktopScreen.screenDividers.ForEach(d => IntegerScalingComboBox.Items.Add(d)); + }); + } + + private void GPUManager_Initialized(GPU GPU) + { + bool HasRSRSupport = false; + if (GPU is AMDGPU amdGPU) + { + amdGPU.RSRStateChanged += OnRSRStateChanged; + HasRSRSupport = amdGPU.HasRSRSupport(); + } + + GPU.IntegerScalingChanged += OnIntegerScalingChanged; + GPU.GPUScalingChanged += OnGPUScalingChanged; + + bool HasScalingModeSupport = GPU.HasScalingModeSupport(); + bool HasIntegerScalingSupport = GPU.HasIntegerScalingSupport(); + bool HasGPUScalingSupport = GPU.HasGPUScalingSupport(); + bool IsGPUScalingEnabled = GPU.GetGPUScaling(); // UI thread (async) Application.Current.Dispatcher.BeginInvoke(() => { + // GPU-specific settings + StackProfileRSR.Visibility = GPU is AMDGPU ? Visibility.Visible : Visibility.Collapsed; + StackProfileRSR.IsEnabled = HasGPUScalingSupport && IsGPUScalingEnabled && HasRSRSupport; StackProfileIS.IsEnabled = HasGPUScalingSupport && IsGPUScalingEnabled && HasIntegerScalingSupport; GPUScalingToggle.IsEnabled = HasGPUScalingSupport; GPUScalingComboBox.IsEnabled = HasGPUScalingSupport && HasScalingModeSupport; - - DesktopScreen desktopScreen = SystemManager.GetDesktopScreen(); - desktopScreen.screenDividers.ForEach(d => IntegerScalingComboBox.Items.Add(d)); }); } - private void SystemManager_StateChanged_RSR(bool Supported, bool Enabled, int Sharpness) + private void OnRSRStateChanged(bool Supported, bool Enabled, int Sharpness) { // UI thread (async) Application.Current.Dispatcher.BeginInvoke(() => @@ -110,7 +127,7 @@ private void SystemManager_StateChanged_RSR(bool Supported, bool Enabled, int Sh }); } - private void SystemManager_StateChanged_GPUScaling(bool Supported, bool Enabled, int Mode) + private void OnGPUScalingChanged(bool Supported, bool Enabled, int Mode) { // UI thread (async) Application.Current.Dispatcher.BeginInvoke(async () => @@ -125,7 +142,7 @@ private void SystemManager_StateChanged_GPUScaling(bool Supported, bool Enabled, }); } - private void SystemManager_StateChanged_IntegerScaling(bool Supported, bool Enabled) + private void OnIntegerScalingChanged(bool Supported, bool Enabled) { // UI thread (async) Application.Current.Dispatcher.BeginInvoke(() => @@ -560,7 +577,7 @@ private void UpdateUI() UpdateMotionControlsVisibility(); // Framerate limit - desktopScreen = SystemManager.GetDesktopScreen(); + desktopScreen = MultimediaManager.GetDesktopScreen(); if (desktopScreen is not null) cB_Framerate.SelectedItem = desktopScreen.GetClosest(selectedProfile.FramerateValue); diff --git a/HandheldCompanion/Views/Pages/SettingsPage.xaml.cs b/HandheldCompanion/Views/Pages/SettingsPage.xaml.cs index 8ec67e7dd..79f2d1660 100644 --- a/HandheldCompanion/Views/Pages/SettingsPage.xaml.cs +++ b/HandheldCompanion/Views/Pages/SettingsPage.xaml.cs @@ -274,7 +274,7 @@ private void Button_DetectNativeDisplayOrientation_Click(object sender, RoutedEv if (!IsLoaded) return; - var rotation = SystemManager.GetScreenOrientation(); + var rotation = MultimediaManager.GetScreenOrientation(); rotation = new ScreenRotation(rotation.rotationUnnormalized, ScreenRotation.Rotations.UNSET); SettingsManager.SetProperty("NativeDisplayOrientation", (int)rotation.rotationNativeBase); } diff --git a/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs index 3a766261b..84c585117 100644 --- a/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickDevicePage.xaml.cs @@ -28,8 +28,8 @@ public QuickDevicePage() { InitializeComponent(); - SystemManager.PrimaryScreenChanged += DesktopManager_PrimaryScreenChanged; - SystemManager.DisplaySettingsChanged += DesktopManager_DisplaySettingsChanged; + MultimediaManager.PrimaryScreenChanged += DesktopManager_PrimaryScreenChanged; + MultimediaManager.DisplaySettingsChanged += DesktopManager_DisplaySettingsChanged; SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; ProfileManager.Applied += ProfileManager_Applied; ProfileManager.Discarded += ProfileManager_Discarded; @@ -58,7 +58,7 @@ private void ProfileManager_Applied(Profile profile, UpdateSource source) // Go to profile integer scaling resolution if (profile.IntegerScalingEnabled) { - DesktopScreen desktopScreen = SystemManager.GetDesktopScreen(); + DesktopScreen desktopScreen = MultimediaManager.GetDesktopScreen(); var profileResolution = desktopScreen?.screenDividers.FirstOrDefault(d => d.divider == profile.IntegerScalingDivider); if (profileResolution is not null) { @@ -163,7 +163,7 @@ private void DesktopManager_DisplaySettingsChanged(ScreenResolution resolution) ComboBoxResolution.SelectedItem = resolution; - int screenFrequency = SystemManager.GetDesktopScreen().GetCurrentFrequency(); + int screenFrequency = MultimediaManager.GetDesktopScreen().GetCurrentFrequency(); foreach (ComboBoxItem comboBoxItem in ComboBoxFrequency.Items) { if (comboBoxItem.Tag is int frequency) @@ -183,7 +183,7 @@ private void ComboBoxResolution_SelectionChanged(object sender, SelectionChanged return; ScreenResolution resolution = (ScreenResolution)ComboBoxResolution.SelectedItem; - int screenFrequency = SystemManager.GetDesktopScreen().GetCurrentFrequency(); + int screenFrequency = MultimediaManager.GetDesktopScreen().GetCurrentFrequency(); ComboBoxFrequency.Items.Clear(); foreach (int frequency in resolution.Frequencies.Keys) @@ -223,13 +223,21 @@ private void SetResolution() int frequency = (int)((ComboBoxItem)ComboBoxFrequency.SelectedItem).Tag; // update current screen resolution - SystemManager.SetResolution(resolution.Width, resolution.Height, frequency, resolution.BitsPerPel); + DesktopScreen desktopScreen = MultimediaManager.GetDesktopScreen(); + + if (desktopScreen.devMode.dmPelsWidth == resolution.Width && + desktopScreen.devMode.dmPelsHeight == resolution.Height && + desktopScreen.devMode.dmDisplayFrequency == frequency && + desktopScreen.devMode.dmBitsPerPel == resolution.BitsPerPel) + return; + + MultimediaManager.SetResolution(resolution.Width, resolution.Height, frequency, resolution.BitsPerPel); } public void SetResolution(ScreenResolution resolution) { // update current screen resolution - SystemManager.SetResolution(resolution.Width, resolution.Height, SystemManager.GetDesktopScreen().GetCurrentFrequency(), resolution.BitsPerPel); + MultimediaManager.SetResolution(resolution.Width, resolution.Height, MultimediaManager.GetDesktopScreen().GetCurrentFrequency(), resolution.BitsPerPel); } private void WIFIToggle_Toggled(object sender, RoutedEventArgs e) diff --git a/HandheldCompanion/Views/QuickPages/QuickHomePage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickHomePage.xaml.cs index a3d1e5eb2..a403cf891 100644 --- a/HandheldCompanion/Views/QuickPages/QuickHomePage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickHomePage.xaml.cs @@ -24,9 +24,9 @@ public QuickHomePage(string Tag) : this() HotkeysManager.HotkeyCreated += HotkeysManager_HotkeyCreated; HotkeysManager.HotkeyUpdated += HotkeysManager_HotkeyUpdated; - SystemManager.VolumeNotification += SystemManager_VolumeNotification; - SystemManager.BrightnessNotification += SystemManager_BrightnessNotification; - SystemManager.Initialized += SystemManager_Initialized; + MultimediaManager.VolumeNotification += SystemManager_VolumeNotification; + MultimediaManager.BrightnessNotification += SystemManager_BrightnessNotification; + MultimediaManager.Initialized += SystemManager_Initialized; ProfileManager.Applied += ProfileManager_Applied; SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; @@ -67,16 +67,16 @@ private void SystemManager_Initialized() // UI thread (async) Application.Current.Dispatcher.BeginInvoke(() => { - if (SystemManager.HasBrightnessSupport()) + if (MultimediaManager.HasBrightnessSupport()) { SliderBrightness.IsEnabled = true; - SliderBrightness.Value = SystemManager.GetBrightness(); + SliderBrightness.Value = MultimediaManager.GetBrightness(); } - if (SystemManager.HasVolumeSupport()) + if (MultimediaManager.HasVolumeSupport()) { SliderVolume.IsEnabled = true; - SliderVolume.Value = SystemManager.GetVolume(); + SliderVolume.Value = MultimediaManager.GetVolume(); UpdateVolumeIcon((float)SliderVolume.Value); } }); @@ -115,7 +115,7 @@ private void SliderBrightness_ValueChanged(object sender, RoutedPropertyChangedE return; using (new ScopedLock(brightnessLock)) - SystemManager.SetBrightness(SliderBrightness.Value); + MultimediaManager.SetBrightness(SliderBrightness.Value); } private void SliderVolume_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) @@ -124,7 +124,7 @@ private void SliderVolume_ValueChanged(object sender, RoutedPropertyChangedEvent return; using (new ScopedLock(volumeLock)) - SystemManager.SetVolume(SliderVolume.Value); + MultimediaManager.SetVolume(SliderVolume.Value); } private void ProfileManager_Applied(Profile profile, UpdateSource source) diff --git a/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs index 0953763de..2e8f8412f 100644 --- a/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickPerformancePage.xaml.cs @@ -48,7 +48,7 @@ public QuickPerformancePage() PerformanceManager.Initialized += PerformanceManager_Initialized; PowerProfileManager.Updated += PowerProfileManager_Updated; PowerProfileManager.Deleted += PowerProfileManager_Deleted; - SystemManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; + MultimediaManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; // device settings GPUSlider.Minimum = MainWindow.CurrentDevice.GfxClock[0]; diff --git a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml index abbbedea3..158112864 100644 --- a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml +++ b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml @@ -10,7 +10,7 @@ Title="{x:Static resx:Resources.QuickProfilesPage_Title}" Margin="15,0,0,0" d:Background="Black" - d:DesignHeight="1500" + d:DesignHeight="2000" d:DesignWidth="640" KeepAlive="True" mc:Ignorable="d"> @@ -139,7 +139,10 @@ - + @@ -205,7 +208,10 @@ - + - + - - + + @@ -692,14 +706,17 @@ - + - - + + @@ -724,18 +741,16 @@ - + - - - - - + + + + + + + - + + + + + + + + + + + + + + + + + + + - + - + diff --git a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs index fb71e0a7e..846cf72f1 100644 --- a/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs +++ b/HandheldCompanion/Views/QuickPages/QuickProfilesPage.xaml.cs @@ -1,5 +1,6 @@ using HandheldCompanion.Actions; using HandheldCompanion.Controls; +using HandheldCompanion.GraphicsProcessingUnit; using HandheldCompanion.Inputs; using HandheldCompanion.Managers; using HandheldCompanion.Managers.Desktop; @@ -48,14 +49,12 @@ public QuickProfilesPage() ProfileManager.Applied += ProfileManager_Applied; PowerProfileManager.Updated += PowerProfileManager_Updated; PowerProfileManager.Deleted += PowerProfileManager_Deleted; - SystemManager.Initialized += SystemManager_Initialized; - SystemManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; - SystemManager.StateChanged_RSR += SystemManager_StateChanged_RSR; - SystemManager.StateChanged_IntegerScaling += SystemManager_StateChanged_IntegerScaling; - SystemManager.StateChanged_GPUScaling += SystemManager_StateChanged_GPUScaling; + MultimediaManager.Initialized += MultimediaManager_Initialized; + MultimediaManager.PrimaryScreenChanged += SystemManager_PrimaryScreenChanged; HotkeysManager.HotkeyCreated += TriggerCreated; InputsManager.TriggerUpdated += TriggerUpdated; PlatformManager.RTSS.Updated += RTSS_Updated; + GPUManager.Initialized += GPUManager_Initialized; foreach (var mode in (MotionOuput[])Enum.GetValues(typeof(MotionOuput))) { @@ -165,29 +164,49 @@ public QuickProfilesPage() RTSS_Updated(PlatformManager.RTSS.Status); } - private void SystemManager_Initialized() + private void MultimediaManager_Initialized() { - bool HasScalingModeSupport = ADLXWrapper.HasScalingModeSupport(); - bool HasIntegerScalingSupport = ADLXWrapper.HasIntegerScalingSupport(); - bool HasRSRSupport = ADLXWrapper.HasRSRSupport(); - bool HasGPUScalingSupport = ADLXWrapper.HasGPUScalingSupport(); - bool IsGPUScalingEnabled = ADLXWrapper.GetGPUScaling(); + // UI thread (async) + Application.Current.Dispatcher.BeginInvoke(() => + { + DesktopScreen desktopScreen = MultimediaManager.GetDesktopScreen(); + desktopScreen.screenDividers.ForEach(d => IntegerScalingComboBox.Items.Add(d)); + }); + } + + private void GPUManager_Initialized(GPU GPU) + { + bool HasRSRSupport = false; + if (GPU is AMDGPU amdGPU) + { + amdGPU.RSRStateChanged += OnRSRStateChanged; + HasRSRSupport = amdGPU.HasRSRSupport(); + } + + GPU.IntegerScalingChanged += OnIntegerScalingChanged; + GPU.GPUScalingChanged += OnGPUScalingChanged; + + bool HasScalingModeSupport = GPU.HasScalingModeSupport(); + bool HasIntegerScalingSupport = GPU.HasIntegerScalingSupport(); + bool HasGPUScalingSupport = GPU.HasGPUScalingSupport(); + bool IsGPUScalingEnabled = GPU.GetGPUScaling(); // UI thread (async) Application.Current.Dispatcher.BeginInvoke(() => { + // GPU-specific settings + StackProfileRSR.Visibility = GPU is AMDGPU ? Visibility.Visible : Visibility.Collapsed; + IntegerScalingTypeGrid.Visibility = GPU is IntelGPU ? Visibility.Visible : Visibility.Collapsed; + StackProfileRSR.IsEnabled = HasGPUScalingSupport && IsGPUScalingEnabled && HasRSRSupport; StackProfileIS.IsEnabled = HasGPUScalingSupport && IsGPUScalingEnabled && HasIntegerScalingSupport; StackProfileRIS.IsEnabled = HasGPUScalingSupport; // check if processor is AMD should be enough GPUScalingToggle.IsEnabled = HasGPUScalingSupport; GPUScalingComboBox.IsEnabled = HasGPUScalingSupport && HasScalingModeSupport; - - DesktopScreen desktopScreen = SystemManager.GetDesktopScreen(); - desktopScreen.screenDividers.ForEach(d => IntegerScalingComboBox.Items.Add(d)); }); } - private void SystemManager_StateChanged_RSR(bool Supported, bool Enabled, int Sharpness) + private void OnRSRStateChanged(bool Supported, bool Enabled, int Sharpness) { // UI thread (async) Application.Current.Dispatcher.BeginInvoke(() => @@ -199,7 +218,7 @@ private void SystemManager_StateChanged_RSR(bool Supported, bool Enabled, int Sh }); } - private void SystemManager_StateChanged_GPUScaling(bool Supported, bool Enabled, int Mode) + private void OnGPUScalingChanged(bool Supported, bool Enabled, int Mode) { // UI thread (async) Application.Current.Dispatcher.BeginInvoke(async () => @@ -214,7 +233,7 @@ private void SystemManager_StateChanged_GPUScaling(bool Supported, bool Enabled, }); } - private void SystemManager_StateChanged_IntegerScaling(bool Supported, bool Enabled) + private void OnIntegerScalingChanged(bool Supported, bool Enabled) { // UI thread (async) Application.Current.Dispatcher.BeginInvoke(() => @@ -490,7 +509,7 @@ private void ProfileManager_Applied(Profile profile, UpdateSource source) } // Framerate limit - desktopScreen = SystemManager.GetDesktopScreen(); + desktopScreen = MultimediaManager.GetDesktopScreen(); if (desktopScreen is not null) cB_Framerate.SelectedItem = desktopScreen.GetClosest(selectedProfile.FramerateValue); @@ -504,6 +523,7 @@ private void ProfileManager_Applied(Profile profile, UpdateSource source) // Integer Scaling IntegerScalingToggle.IsOn = selectedProfile.IntegerScalingEnabled; + IntegerScalingTypeComboBox.SelectedIndex = selectedProfile.IntegerScalingType; if (desktopScreen is not null) IntegerScalingComboBox.SelectedItem = desktopScreen.screenDividers.FirstOrDefault(d => d.divider == selectedProfile.IntegerScalingDivider); @@ -976,6 +996,19 @@ private void IntegerScalingComboBox_SelectionChanged(object sender, SelectionCha UpdateProfile(); } + private void IntegerScalingTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (IntegerScalingTypeComboBox.SelectedIndex == -1 || selectedProfile is null) + return; + + // wait until lock is released + if (updateLock) + return; + + selectedProfile.IntegerScalingType = (byte)IntegerScalingTypeComboBox.SelectedIndex; + UpdateProfile(); + } + private enum UpdateGraphicsSettingsSource { GPUScaling, diff --git a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs index a5dd3788d..82475ecb1 100644 --- a/HandheldCompanion/Views/Windows/MainWindow.xaml.cs +++ b/HandheldCompanion/Views/Windows/MainWindow.xaml.cs @@ -1,5 +1,6 @@ using HandheldCompanion.Controllers; using HandheldCompanion.Devices; +using HandheldCompanion.IGCL; using HandheldCompanion.Inputs; using HandheldCompanion.Managers; using HandheldCompanion.Utils; @@ -224,7 +225,7 @@ public MainWindow(FileVersionInfo _fileVersionInfo, Assembly CurrentAssembly) // manage events InputsManager.TriggerRaised += InputsManager_TriggerRaised; - PowerManager.SystemStatusChanged += OnSystemStatusChanged; + SystemManager.SystemStatusChanged += OnSystemStatusChanged; DeviceManager.UsbDeviceArrived += GenericDeviceUpdated; DeviceManager.UsbDeviceRemoved += GenericDeviceUpdated; ControllerManager.ControllerSelected += ControllerManager_ControllerSelected; @@ -241,13 +242,14 @@ 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(); + GPUManager.Start(); // todo: improve overall threading logic new Thread(() => { PlatformManager.Start(); }).Start(); @@ -561,17 +563,17 @@ public void UpdateSettings(Dictionary args) } // no code from the cases inside this function will be called on program start - private async void OnSystemStatusChanged(PowerManager.SystemStatus status, PowerManager.SystemStatus prevStatus) + private async void OnSystemStatusChanged(SystemManager.SystemStatus status, SystemManager.SystemStatus prevStatus) { if (status == prevStatus) return; switch (status) { - case PowerManager.SystemStatus.SystemReady: + case SystemManager.SystemStatus.SystemReady: { // resume from sleep - if (prevStatus == PowerManager.SystemStatus.SystemPending) + if (prevStatus == SystemManager.SystemStatus.SystemPending) { // use device-specific delay await Task.Delay(CurrentDevice.ResumeDelay); @@ -602,7 +604,7 @@ private async void OnSystemStatusChanged(PowerManager.SystemStatus status, Power } break; - case PowerManager.SystemStatus.SystemPending: + case SystemManager.SystemStatus.SystemPending: // sleep { // stop the virtual controller @@ -680,7 +682,8 @@ private void Window_Closed(object sender, EventArgs e) overlayquickTools.Close(true); VirtualManager.Stop(); - SystemManager.Stop(); + MultimediaManager.Stop(); + GPUManager.Stop(); MotionManager.Stop(); SensorsManager.Stop(); ControllerManager.Stop(); @@ -691,7 +694,7 @@ private void Window_Closed(object sender, EventArgs e) PowerProfileManager.Stop(); ProfileManager.Stop(); LayoutManager.Stop(); - PowerManager.Stop(); + SystemManager.Stop(); ProcessManager.Stop(); ToastManager.Stop(); TaskManager.Stop(); diff --git a/HandheldCompanion/Views/Windows/OverlayQuickTools.xaml.cs b/HandheldCompanion/Views/Windows/OverlayQuickTools.xaml.cs index 0f9267f3d..57fe7610e 100644 --- a/HandheldCompanion/Views/Windows/OverlayQuickTools.xaml.cs +++ b/HandheldCompanion/Views/Windows/OverlayQuickTools.xaml.cs @@ -25,7 +25,7 @@ using KeyEventArgs = System.Windows.Input.KeyEventArgs; using Page = System.Windows.Controls.Page; using PowerLineStatus = System.Windows.Forms.PowerLineStatus; -using PowerManager = HandheldCompanion.Managers.PowerManager; +using SystemManager = HandheldCompanion.Managers.SystemManager; using Screen = WpfScreenHelper.Screen; using SystemPowerManager = Windows.System.Power.PowerManager; using Timer = System.Timers.Timer; @@ -98,9 +98,9 @@ public OverlayQuickTools() WM_PAINT_TIMER.Elapsed += WM_PAINT_TIMER_Tick; // create manager(s) - PowerManager.PowerStatusChanged += PowerManager_PowerStatusChanged; + SystemManager.PowerStatusChanged += PowerManager_PowerStatusChanged; - SystemManager.DisplaySettingsChanged += SystemManager_DisplaySettingsChanged; + MultimediaManager.DisplaySettingsChanged += SystemManager_DisplaySettingsChanged; SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; // create pages @@ -211,7 +211,7 @@ private void PowerManager_PowerStatusChanged(PowerStatus status) // set key var Key = $"Battery{KeyStatus}{KeyValue}"; - if (PowerManager.PowerStatusIcon.TryGetValue(Key, out var glyph)) + if (SystemManager.PowerStatusIcon.TryGetValue(Key, out var glyph)) BatteryIndicatorIcon.Glyph = glyph; if (status.BatteryLifeRemaining > 0) From 009d55581527249e9566a281f406d703835136cd Mon Sep 17 00:00:00 2001 From: Lesueur Benjamin Date: Sat, 13 Jan 2024 18:26:23 +0100 Subject: [PATCH 04/39] Implement new UI classes (#153) * Implement new UI classes - UISounds to manage UI sounds on interaction. - UIGamepad to manage gamepad interactions. - Audio files from https://kenney.nl/assets/ui-audio. - Add support for TextBox and RepeatButton selection via gamepad. * Update HandheldCompanion/UI/UISounds.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix PlayOggFile refs * removed unused audio files * Add UI Sounds toggle on SettingsPage (default Off) --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- HandheldCompanion/App.config | 5 +- .../Hints/Hint_AMD_IntegerScalingCheck.cs | 4 +- .../Controls/Hints/Hint_CoreIsolationCheck.cs | 4 +- HandheldCompanion/HandheldCompanion.csproj | 24 ++- .../Managers/ControllerManager.cs | 4 +- HandheldCompanion/Managers/HotkeysManager.cs | 3 +- HandheldCompanion/Managers/UpdateManager.cs | 7 +- HandheldCompanion/Misc/Dialog.cs | 43 ++-- .../Properties/Resources.Designer.cs | 18 ++ HandheldCompanion/Properties/Resources.resx | 6 + .../Properties/Settings.Designer.cs | 12 ++ .../Properties/Settings.settings | 9 +- HandheldCompanion/UI/Audio/bong_001.ogg | Bin 0 -> 4848 bytes HandheldCompanion/UI/Audio/drop_001.ogg | Bin 0 -> 5859 bytes HandheldCompanion/UI/Audio/drop_002.ogg | Bin 0 -> 6411 bytes HandheldCompanion/UI/Audio/glitch_004.ogg | Bin 0 -> 5225 bytes HandheldCompanion/UI/Audio/switch_004.ogg | Bin 0 -> 7248 bytes HandheldCompanion/UI/Audio/switch_005.ogg | Bin 0 -> 7814 bytes HandheldCompanion/UI/Audio/switch_007.ogg | Bin 0 -> 7921 bytes .../UIGamepad.cs} | 54 +++-- HandheldCompanion/UI/UISounds.cs | 202 ++++++++++++++++++ HandheldCompanion/Utils/WPFUtils.cs | 49 ++++- .../Views/Classes/GamepadWindow.cs | 57 ++++- .../Views/Pages/ControllerPage.xaml.cs | 3 +- .../Views/Pages/DevicePage.xaml.cs | 2 +- .../Views/Pages/LayoutPage.xaml.cs | 6 +- .../Views/Pages/PerformancePage.xaml.cs | 2 +- .../Views/Pages/ProfilesPage.xaml.cs | 6 +- .../Views/Pages/SettingsPage.xaml | 38 ++++ .../Views/Pages/SettingsPage.xaml.cs | 13 +- .../QuickPages/QuickPerformancePage.xaml.cs | 3 +- .../Views/Windows/MainWindow.xaml | 25 ++- .../Views/Windows/MainWindow.xaml.cs | 4 + .../Views/Windows/OverlayQuickTools.xaml | 15 +- .../Views/Windows/OverlayQuickTools.xaml.cs | 7 + 35 files changed, 543 insertions(+), 82 deletions(-) create mode 100644 HandheldCompanion/UI/Audio/bong_001.ogg create mode 100644 HandheldCompanion/UI/Audio/drop_001.ogg create mode 100644 HandheldCompanion/UI/Audio/drop_002.ogg create mode 100644 HandheldCompanion/UI/Audio/glitch_004.ogg create mode 100644 HandheldCompanion/UI/Audio/switch_004.ogg create mode 100644 HandheldCompanion/UI/Audio/switch_005.ogg create mode 100644 HandheldCompanion/UI/Audio/switch_007.ogg rename HandheldCompanion/{Managers/GamepadFocusManager.cs => UI/UIGamepad.cs} (94%) create mode 100644 HandheldCompanion/UI/UISounds.cs diff --git a/HandheldCompanion/App.config b/HandheldCompanion/App.config index a3ffdcfcf..b8d5679aa 100644 --- a/HandheldCompanion/App.config +++ b/HandheldCompanion/App.config @@ -225,7 +225,7 @@ False - False + False 0 @@ -251,6 +251,9 @@ 2 + + False + \ No newline at end of file diff --git a/HandheldCompanion/Controls/Hints/Hint_AMD_IntegerScalingCheck.cs b/HandheldCompanion/Controls/Hints/Hint_AMD_IntegerScalingCheck.cs index 74f017a12..5111b40e6 100644 --- a/HandheldCompanion/Controls/Hints/Hint_AMD_IntegerScalingCheck.cs +++ b/HandheldCompanion/Controls/Hints/Hint_AMD_IntegerScalingCheck.cs @@ -2,6 +2,8 @@ using HandheldCompanion.Misc; using HandheldCompanion.Processors; using HandheldCompanion.Utils; +using HandheldCompanion.Views; +using HandheldCompanion.Views.Windows; using iNKORE.UI.WPF.Modern.Controls; using System; using System.Diagnostics; @@ -55,7 +57,7 @@ protected override async void HintActionButton_Click(object sender, RoutedEventA $"{Properties.Resources.Dialog_ForceRestartDesc}", ContentDialogButton.Primary, null, $"{Properties.Resources.Dialog_Yes}", - $"{Properties.Resources.Dialog_No}"); + $"{Properties.Resources.Dialog_No}", MainWindow.GetCurrent()); await result; diff --git a/HandheldCompanion/Controls/Hints/Hint_CoreIsolationCheck.cs b/HandheldCompanion/Controls/Hints/Hint_CoreIsolationCheck.cs index 8630a0700..11a7fadc6 100644 --- a/HandheldCompanion/Controls/Hints/Hint_CoreIsolationCheck.cs +++ b/HandheldCompanion/Controls/Hints/Hint_CoreIsolationCheck.cs @@ -1,5 +1,7 @@ using HandheldCompanion.Misc; using HandheldCompanion.Utils; +using HandheldCompanion.Views; +using HandheldCompanion.Views.Windows; using iNKORE.UI.WPF.Modern.Controls; using System; using System.Diagnostics; @@ -72,7 +74,7 @@ protected override async void HintActionButton_Click(object sender, RoutedEventA $"{Properties.Resources.Dialog_ForceRestartDesc}", ContentDialogButton.Primary, null, $"{Properties.Resources.Dialog_Yes}", - $"{Properties.Resources.Dialog_No}"); + $"{Properties.Resources.Dialog_No}", MainWindow.GetCurrent()); await result; diff --git a/HandheldCompanion/HandheldCompanion.csproj b/HandheldCompanion/HandheldCompanion.csproj index a498ebe41..28b59e41f 100644 --- a/HandheldCompanion/HandheldCompanion.csproj +++ b/HandheldCompanion/HandheldCompanion.csproj @@ -154,7 +154,8 @@ - + + @@ -2270,6 +2271,27 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/HandheldCompanion/Managers/ControllerManager.cs b/HandheldCompanion/Managers/ControllerManager.cs index a10bfb901..7aa90dcbf 100644 --- a/HandheldCompanion/Managers/ControllerManager.cs +++ b/HandheldCompanion/Managers/ControllerManager.cs @@ -72,8 +72,8 @@ public static Task Start() SettingsManager.SettingValueChanged += SettingsManager_SettingValueChanged; - GamepadFocusManager.GotFocus += GamepadFocusManager_GotFocus; - GamepadFocusManager.LostFocus += GamepadFocusManager_LostFocus; + UIGamepad.GotFocus += GamepadFocusManager_GotFocus; + UIGamepad.LostFocus += GamepadFocusManager_LostFocus; ProcessManager.ForegroundChanged += ProcessManager_ForegroundChanged; diff --git a/HandheldCompanion/Managers/HotkeysManager.cs b/HandheldCompanion/Managers/HotkeysManager.cs index aa0733db3..96dd881ed 100644 --- a/HandheldCompanion/Managers/HotkeysManager.cs +++ b/HandheldCompanion/Managers/HotkeysManager.cs @@ -6,6 +6,7 @@ using HandheldCompanion.Simulators; using HandheldCompanion.Utils; using HandheldCompanion.Views; +using HandheldCompanion.Views.Windows; using iNKORE.UI.WPF.Modern.Controls; using Newtonsoft.Json; using System; @@ -276,7 +277,7 @@ private static void PinOrUnpinHotkey(Hotkey hotkey) { _ = Dialog.ShowAsync($"{Resources.SettingsPage_UpdateWarning}", $"You can't pin more than {PIN_LIMIT} hotkeys", - ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}"); + ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}", string.Empty, MainWindow.GetCurrent()); return; } diff --git a/HandheldCompanion/Managers/UpdateManager.cs b/HandheldCompanion/Managers/UpdateManager.cs index faa46f28d..8096faf5f 100644 --- a/HandheldCompanion/Managers/UpdateManager.cs +++ b/HandheldCompanion/Managers/UpdateManager.cs @@ -1,6 +1,7 @@ using HandheldCompanion.Misc; using HandheldCompanion.Properties; using HandheldCompanion.Views; +using HandheldCompanion.Views.Windows; using iNKORE.UI.WPF.Modern.Controls; using Newtonsoft.Json; using System; @@ -146,13 +147,13 @@ private static void WebClient_DownloadStringCompleted(object sender, DownloadStr _ = Dialog.ShowAsync($"{Resources.SettingsPage_UpdateWarning}", Resources.SettingsPage_UpdateFailedDownload, - ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}"); + ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}", string.Empty, MainWindow.GetCurrent()); } else { _ = Dialog.ShowAsync($"{Resources.SettingsPage_UpdateWarning}", Resources.SettingsPage_UpdateFailedGithub, - ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}"); + ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}", string.Empty, MainWindow.GetCurrent()); } status = UpdateStatus.Failed; @@ -305,7 +306,7 @@ public static void InstallUpdate(UpdateFile updateFile) { _ = Dialog.ShowAsync($"{Resources.SettingsPage_UpdateWarning}", Resources.SettingsPage_UpdateFailedInstall, - ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}"); + ContentDialogButton.Primary, string.Empty, $"{Resources.ProfilesPage_OK}", string.Empty, MainWindow.GetCurrent()); return; } diff --git a/HandheldCompanion/Misc/Dialog.cs b/HandheldCompanion/Misc/Dialog.cs index c83ebe220..54db97611 100644 --- a/HandheldCompanion/Misc/Dialog.cs +++ b/HandheldCompanion/Misc/Dialog.cs @@ -1,32 +1,43 @@ -using iNKORE.UI.WPF.Modern.Controls; +using HandheldCompanion.Utils; +using HandheldCompanion.Views; +using HandheldCompanion.Views.Windows; +using iNKORE.UI.WPF.Modern.Controls; using System.Threading.Tasks; +using System.Windows; namespace HandheldCompanion.Misc; internal class Dialog { - public static async Task ShowAsync(string Title, string Content, - ContentDialogButton DefaultButton = ContentDialogButton.Primary, string CloseButtonText = null, - string PrimaryButtonText = null, string SecondaryButtonText = null) + public static async Task ShowAsync(string Title, string Content, ContentDialogButton DefaultButton = ContentDialogButton.Primary, + string CloseButtonText = null, string PrimaryButtonText = null, string SecondaryButtonText = null, Window owner = null) { try { - var dialog = new ContentDialog + // I hate my life... Improve me! + ContentDialog dialog = null; + switch (owner.Tag) { - Title = Title, - Content = Content, - CloseButtonText = CloseButtonText, - PrimaryButtonText = PrimaryButtonText, - SecondaryButtonText = SecondaryButtonText, - DefaultButton = DefaultButton - }; + default: + case "MainWindow": + dialog = MainWindow.GetCurrent().ContentDialog; + break; + case "QuickTools": + dialog = OverlayQuickTools.GetCurrent().ContentDialog; + break; + } - var result = await dialog.ShowAsync(); + dialog.Title = Title; + dialog.Content = Content; + dialog.CloseButtonText = CloseButtonText; + dialog.PrimaryButtonText = PrimaryButtonText; + dialog.SecondaryButtonText = SecondaryButtonText; + dialog.DefaultButton = DefaultButton; + + ContentDialogResult result = await dialog.ShowAsync(owner); return result; } - catch - { - } + catch { } return ContentDialogResult.None; } diff --git a/HandheldCompanion/Properties/Resources.Designer.cs b/HandheldCompanion/Properties/Resources.Designer.cs index e3d9db158..f82ea0029 100644 --- a/HandheldCompanion/Properties/Resources.Designer.cs +++ b/HandheldCompanion/Properties/Resources.Designer.cs @@ -7949,6 +7949,24 @@ public static string SettingsPage_ToastNotificationDesc { } } + /// + /// Looks up a localized string similar to Enable UI sounds. + /// + public static string SettingsPage_UISounds { + get { + return ResourceManager.GetString("SettingsPage_UISounds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable or disable UI sounds on navigation. + /// + public static string SettingsPage_UISoundsDesc { + get { + return ResourceManager.GetString("SettingsPage_UISoundsDesc", resourceCulture); + } + } + /// /// Looks up a localized string similar to Updates available. /// diff --git a/HandheldCompanion/Properties/Resources.resx b/HandheldCompanion/Properties/Resources.resx index 2e9a82158..2347d78a6 100644 --- a/HandheldCompanion/Properties/Resources.resx +++ b/HandheldCompanion/Properties/Resources.resx @@ -2811,6 +2811,12 @@ Motion input toggle: press selected button(s) to switch motion state. 1/1 only supports Exclusive Fullscreen. + + Enable UI sounds + + + Enable or disable UI sounds on navigation + Scaling type diff --git a/HandheldCompanion/Properties/Settings.Designer.cs b/HandheldCompanion/Properties/Settings.Designer.cs index 53819a602..36c1f2436 100644 --- a/HandheldCompanion/Properties/Settings.Designer.cs +++ b/HandheldCompanion/Properties/Settings.Designer.cs @@ -1004,5 +1004,17 @@ public int LastOnScreenDisplayLevel { this["LastOnScreenDisplayLevel"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool UISounds { + get { + return ((bool)(this["UISounds"])); + } + set { + this["UISounds"] = value; + } + } } } diff --git a/HandheldCompanion/Properties/Settings.settings b/HandheldCompanion/Properties/Settings.settings index 825e9dcf7..e3f51a01d 100644 --- a/HandheldCompanion/Properties/Settings.settings +++ b/HandheldCompanion/Properties/Settings.settings @@ -218,9 +218,9 @@ False - - False - + + False + 0 @@ -248,5 +248,8 @@ 2 + + False + \ No newline at end of file diff --git a/HandheldCompanion/UI/Audio/bong_001.ogg b/HandheldCompanion/UI/Audio/bong_001.ogg new file mode 100644 index 0000000000000000000000000000000000000000..562ddb329e06f5913232c866909eecf6ece87f7d GIT binary patch literal 4848 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 g)D{BRYi{aKNYeUGs6j|C(+4qX5 z$fUW2BxITQ%&7ahpXdI3p7-zHEZ6tCu5(?#F?DyhfT*Eg<+a#eCxGUL z3Kc(9sDGfdXApUaTJQeejBf!QrSp)=l)Un*OS-35I-ozGDA7c>-9IEqj>s&Mp}promi^ChUH)l$i2>^i8(UWMW5nz)N!NkfUZUq4)o{($X%fDfc?AQaDmCO6IrLf2rRxy~2(La6k1 zAfwd1f~$-QF9c&v6c+Ago>pDBSDdQ)MY3T~mr1r^*vJGicE+Wujn*MDs9%L}`&Yw2 zkwXyNMVFS_l}(qPynO<^BqtDTwI`M!gaKkgr*3i>)^haJ^7RdgntqcpKcqNx?4*Sy z$_kVa2kTI`iO`UV&?wuSc>9M@wh!a&U&lMF$2-BU{i+*LGwb9^5nYf9DiBf0o`n|) z^AvqlDcToz?LIZ+N`^#`Aww9RQEXUp!K=zMx5=ZT$+xFYrl*dX!UD8UL%t1E;E5~w zN4;R1?e@P{ZI2gHkT$@wJAkh{K=dS9v^!9Sj)L$D2I$TcK;KV4`4;&+g$Hv1_#jFbiMtxkAXb)pXQ?t^yc&$)^+EqU{ort zGlx)#P$qwH8cSvY;syLJe@d#pQCX}Rnp*-Ne3c@(AOyv+QLOlPLoww&E54hNB=t(N zzE`0~hTPnL)~`yedB%2v83C+VMiE$XO5=Q9P?d^-H@#XucWkhM6bVP$D$9qMcQN66%Zc!$>^ zGZRrW6Df{d*Z$M6{_-3MI87YIC$Y}Si0<35r*#?j6#UzB0yw&F^1Zw%YFsCZ8kXt% zhBRG84jnpajzn2-It+1!j2w2eP;wYibQ`g98w+)N)8uCVAj-Dsm&5G2&CEo|-#mxx zBEl-Evj!P7|MHvy$<&X^sUn6MqP7|0VcG79xUyS$3uQ%&|KT|vNu@bSr3p!dgjC7I zY|lhod2^A^t14p4|62d@oKu0KpaRc187TTU&v}gCI|00@P6oBQC!@v?05td{*WVQY zL66g!jVbSlIa+oMtvH63wX)Rs?;ZnY$B?KIBmg!Gg1CYGbkY?jquDb@?f!gXlZ2qh z(|$t4bHrxHb?4~5GCy79!YK6VXz8%Zwd9*`of&T_=-RGmgeD7J*MQy8{VYlgo;@1K z9D-;Kg$+etep4?x^<8FQHc|Z`W-bhwLNLrbwR)K!GW>p-AC;+olQ4t?QG$vE{2kUU z6q2UKC{sZ8Pj7Z+=V1$A2PAX^C?oc50K)x-=v1Q zC3&rhg;jGE@o9+0kfl}8zesR5gS-o5W=|ZP{p3iTryb6HkMy9B;U%BTO-8J46_ukM zCgO?VMP)5utHhG>Kr29kxIAbw6p~m}!G6-p4OhiDHl{ZM$S)MKoXLvQ$|Ceu2~hzx zfiJIOdc%nt%lbvOR54l8g4CERO)-E0;1Po1i*aK4NK=?P?45E%LyXm$%fA zhk@ULh~IvE>{QIa#gF-IP#8o4qBF;d(FiVvOwxgiVd-@Z%I;zf4P+a~6(r%{!4ktaoUwa# z_wG2_`xbW8&btzVYWC5AKc+&8T>KCen~o+Y3nvZWU9g~3;kz6z$ttG~qLUDW+r?O^ zPFPUCxlxvLvI;>GZ-nT|3Bsa5X3?u+REAAP%=3W<1GWs%U1~^~uN&Yz0%R2Hu9X2m0Z@7ZWO^t-WV(3*)X6yZ43l*LKrC6HAo!)l za*T|&)!5%qp@QBsut3`TmF}lrL?aK|c)T!z1ouH01g(%Ov+fI6967CsV`GP)qwhhM zbY7QR66q=ir87d>uwo9EenZ1D1${U{5Ibz=mX7Y%k;FG%fa7n+W}v;#|gt?f&@#NI31WU#}$HEBU z4)81mxFhCoQxdnJeoFSHLZGB@APAC45DCd7$fn54KvF_YHgMjPN)j*zDYA@g;K_hJ z!v+DN#IhKLx&(vCQdrRgXyU*<*AN11tmLCFAdo+9e~D2(B`K**Uou1ZD3^AaZ4&3gN6byb0#*+pkg@ z;e!~r@dhC~-~L7=8HS@_7a@#MO~AFRyY)|p-cxXBrh<}z;PbyjyCEJ}7`PZkaaber z#GVRuCAq-_-QGl;nbE>u6X5Q{Dg?n_s@8?$$@2W-LmU0>fn9)cQmlc}fVlm81%Lr< z#PDkco311t%l?Z`3>*Z~Xt1`B%v?wg_W(U|!v+ol`$u4+E)lswd& zW#^RwSZ>I7!JNGBD|Ug`qZ1|)SJT+UZdI1-p&8Z-=Q^%zvx_G?%n<2~; zJstFXzX(HFJ+pMm!xb9lg9e2{6%YmnVT7PK=Q@A_c=#ZyGd0rn4PM0$%Q9O($3B(_ zetyCA1>UlEPH#{|anxDtbs^`}`~Bz1K@U=Zpy#%ID&4oG=k7MX_LAX5vw&wk^o8Ij z(M~%BSGL$u=Pw)z2|8Y7!OYw=VqKQ1@yxMjU8D$$ zb{@R5ENtv|AggL8nVdF5TY?}63LB&eK^dHa!nj*1sVr%%>1;O-X0T@-!g7E#O`Gzq z4zX!N)YQOXKp}@xYHG?iJ&l(qWe6gF)01Z<)35xLogHs}R83m+MDQHp+%5EZTHE<24#D)qbbja`@c zaMx|jsTYrytgQJ<=l%I4uVcnHr)Tv?NaUHRbCU{7S{=KZ+*(^<{>>9hlTqKA-i7h9 z$uc$8%jc-Kl~)sU_6@|#S6`#1b%e~)@qH<-8W|5zJko6p9izeRe>P1-&B6{x)Y~IB zRw5s|V`N*hkXhlU9LpU~KJq3W%vx+|KKHshZ$2j5Ao^p6S5&mq>Zt5ccqFgkYHQ&U z>C;yW!@Eq*mOfh_W_sQ$BoD>?yufMRsD{Ztz*U~x{D;mIo2PrM;+gH)O_6M;4{IMx zE3#(VHM_eG94Go0Zjz zoAYO_W71r_Efe?AF4y*-eO>Ea`90v&yJ)!`?-55AOTliI5H_6znyhEdZLuQO?_MeJ zeE1qVbtvMraX5a?N`3B>J;U`1iTbtA$6B3_Tos-_CmCmA_=X={$t5zHJX`p~T}>6G zW$pTs!0mPB&>}uCal@B5~3rV+8XC~LZAPvRCI33k|FBK zacADtk(2J>sg*k^XXq-*7N&gqzVUqJq>O<53k=k{JIoDh=1iz=KCoE6zFLB;YQ}w8 z^*$W>lw=_Ej(1({A<45eVCJ>=%R+Ia6&>&OqOU*ZSj0nPS#+E}y>dT3?v(z=mj^4= z0l5aLx6aIL9~L;VFf#dRB{~ND7^n*0j`=zpX=CxY=1T7e(yDyOPp8LJRiB>hi%<4> zreiC~!CkyB#sxUq*5_ z8{cm6cSI3?l=^GsYmnS0?M1FFg!hDZEbN49O-}Q`QO|-V+%(TExleX9E^1**T<2;X z35|_yjSeh$BYgqT-Lapvw^nIDkGA&{Tl*{SSDFPczJ0d6y}r6u+c3O(Su;<*I*s7s z8NaYVn0+HK&^;_-hB7tnI6=3|tsQ6tXV`o1{G07=1fXQ9V$`o-tURaf$n(3+`H-=XC4JuGp}m1NKLjpG$-u%qe`@@S3_B zV;#!B&&bsFeQ$g0P+Qqt`^HzBvd&;UJ9URcY3h3W^KY%h6Y6rt0KZnB<;l6GW&BmMr|#cW$?y9N`&5xt#in~3lv!=6%IdIW!xW60^AjZdMZodNNt z!$JZAq>k<-pG)B~wzR=F)6z<3GqqxlPb}cfzqOpzs?s89lByo@jDG&HM#w6zCcOx z{gl-f;ZdKCui4|xIx@l4^lk`))mkuUownH!dHis2tKl&i^{ z(~|xn4ed?Fx+VCQ%RyQ;d$I4o4F2r^@R18aSg&p6V@CV`aR%jd_93c&Oi+H2L{JC6 zt_wJC#@8g&RMrA*f2?Uui&^<|pgLx#<6g%c_rNAMmjsuDwZ>L?e$-ffHBp^7=H52X zJwU8)uUc)*TR#@vUmtyLV72AvT#@XS$~)$x7cY1z6Ju=0yDgUw@$wSZN!2kQ1h((y zDpM&H;L~^CG&8h$V%B^QO%YYL+NYtFy0-o^>RHPh`TXmny#@}X zoz?u97`tQ;8CRnY;NY&>Q>giD$Hvg^8udGJ*C$stUne}zZ@TQ4-5B9tC-tp|#+HZT zjQhYxuUgg(TfaK-o}~zO6{_39xlDMcR1Zl7uW@DG{EM^R3aMu5`x^Mq?`&Uo+iBnV z7S^8F!CzF&n^%Ef^Nd*-nRVP*!@hBfl_sn>pJ=FGPqk?M12GnCaimy4-{>Gu{&k|U z*6y;H2yyn&+r`TeORHa1b4tLO_xX-0+R+lrJev$Us$b9R4t^3}MHmwWj(2Z9owjk} z;XVFwQ|Dy=iWOg{bWC3Mbc*eKOs(tIv&rLwosTa4glY6gj#-50_^8c~m%8-Ykv`{{ z8QTkJ+Ot?2_CM%e%QC?8pm&uazMp(0&ZVKXI@cE)A6mD26sZleSVU|Mh_w5DTI<-E zWWAxbJvnwc>8UpYN$}h2<6j>&>`ss#z2T%&-cK`y+-5xYC1EF^_sXfU$Vje( z=XaJCAo?eI3qw2GCdS15NoEuM+}vj(b+6y)$X_S04xSa#FV&lb*Q!dD+l4I`8_!mE zDI@X*rgg=m<<0K2Wv{x8h8<3`|Lj~*7*9RoAwU0%-!($rGg3j%(eg{9+y@`FF%DnIdGDy1#D? zWOQ_(@0*gk!*L24yNlINu7)IfOOuoTVpR}w`ibXx-w+MO3 z039(1oo=thqjt4+&6(zVSaEc@EhqLLdKB$HN8}A}zB3{tiz6vE8uEQxKi~&Jb}HL3 z{3A#$hbJ{xj6afAO~)9XV)mWwN&2X(FTl*?Rw#qruXd__ghg$!X5=esYS~a==e2(a z=?#fUNF#vQdpvrpXJTgeXNMMPjMaC#x>7#B_JM!Y&i6$^J%QvC;r-mXWx7tzG^``f z-`787gY_DrY9z9CdY~!Bdoo56_40*FN58js1X1cb`s5wWe&W<_}ZbVp=Oyzg=yfnh>=4H)n{(Ir!Vd`76wx(&&$jHdzp%+kwh0(8{_+SnC zf)^(R-dy0$Vo_cEBICXMwu0&{Kil0`nN$9BnZ<)CJaWXB9`TNj%eh!LU$v-7_*UKr zQ~&iE>!{)Rf)wfO%A-s8(We{cqO!|Xw5*?{pM3psflyx9JalQ|-GJE_O@XpY83)g8 zihY|8JMQqQ7ytaN>>bT5gQ*chm%*Bn!U1&nxg$QW>}DfxU}3&r8{Q2YNq^Z!-$iHH zJvrxN+kU*|b4Ss}U4NeE@()PYIYE&7m+ai#5mkRDY4_56S7%6*NH`#Vh{_E_@Wy@XSTKJXj zS*mmL@2NK0t+tkm`?QaMKwR-dE8myJE0(&Si>Wi7gC6cm98_&!sf>-^`vfEhEXbyY zT`)jq;=_GKvD~Aj`7KZDEKXrFBKK5|(}8x2h_8z@A=*@gythOTu6z3!l_x#e1%XO_B}@>;jy{h=icK((%858-Wm1&yg#4!@Av&azW;o$JJ&t0d(XZ1JkL4zoY##0jT=S~HT0|eDYVxy zighMZ@lg4?d)hjCksncCD%%@z&84Swo>A$OIsYnTPAWhJSqd8+vM2p}F{0U9=`@&N z=6u^pT;KBs-z{g`E0p#5H2EaXA|%dAoIS@UqVMYFeB0B(%fZbXPF|0EAR~82?>jc0 z4vIRux<-5=fYHm%-qHD%gBU{mthj_Um@fsV+V^V@)r(iCAO^sO(BW%EzfUlMAXW&v zE(m8ya@2%llk-IU(~~vGz1Fjl>B*6;FuNE;+wX=?(*6hp(L!hu#<+qh&70k_H(xK(495CMp@PSk`_`}$d{`&^#w z{KZ+GOs5{@-eZzz=L^x1m@Q1wlbd~15GVIdr24(;ezEF4Egi(b6}yTiI?KCW-LlhJ zzZyo09DLv`s&t(8tf~y;7HPzJ+b&8G!PR~f55K!guUYlS68o~{!dZE!&1HH zE*lx^nt=W&;cVRo78tYvf81osSC2Jt3Pq z0nZmWmj6XIpC&ZCj2f~hL&C?HC;(3^&@6JiS>c>n>r`6n+VNDh<0&&`3eY|+`7}_j zGp6Vt)zLiF;eW0wPVJ{46@Vq)gA4B=co`{(_Y|e4AlwcCovI*3TRkP!y(IBo3ZSZc zUwWIY)3vN~@V64kYKI^d1XrsE*IR%FQmo%o%G67d;H66dP>>P%@5>()djW#*KXgpJ ziDo#LU_*fg4VH!FF$+BQ3b8+mM{VyYpGllDM>6sJ_-s+pne^qjKJbilI*X-^(+13Q?!(aIKAHhu^Y@skNA>KW z7nz(XTvKnnx85(az*%K=zmy#Z9FfDBF7=F-ptoV4wa~>#e)gZ{qXP71GX!gUc#jVi zM`z2>~*)?+~e1*7nu>jibbV>701?0WqVb~YPeu)1}#owlaX3*?4CyfrSjP) zT%hBmI7xWA=R0^Y@QS}H?uTH{A@1J4)t)6TLpC8$dnLq-&MBH0o0$5#ny2~vSr=wG zSHK2slkB#V3<(V-WbP5It4KJq7>v91nK<1Fp9Z1hth_6t z{gl)vO7(*DbHaz*04$i$t{OcXRvJn5f$BNBtl$ezXJGyR9>I7JnwM-@jzO-ICu zM5a1NVoK`sZ@sISYxp1QU!J4xDF`a?oXehqfAgFd2(F93o1Tj568B_O>;-^&U*`C` z0wCx`0<$*d9x+6U4IrfkkYXmrivPXGfYAX--F`^`YzhQ%0{dyD&x=8_C4I2?{?aT8 z!GNWkM1--2q=w?tRo@xv<=ZjwPo6FAlMRn~Fl@_|A)#u%r05&Ne_s*yP<2L-4qSV* zkRb%oX$oixzWu3?uf8nWGZCq92>sPxGIm-sTb<~~18FY%@#rQgJecm41W|&D1$>UG z=JB7VM$1q^(SrUwnm-j10fR0(e^hEwBu~%b%h@NRF7-?1VN#3y1ptW=j?yudvcY7} zWSs2xHYviDTr*GQZ_F0Poa{HY1f;!bDt`xXdr|#!x){eO$u7yreCJn^VqJhdiTO<` z8XA*%b&O2vE9PGLDE1ng4dr1JG6J|P6fE@WCCNA*r z*85~Lu3WMasAxdwRuA&@F5;zLN!_Hc8i_^z+0y{Nl2e*w6I1}~{l+G#_U2&Sd?!pv zNyB6EW8ilnVmV=oBJ)KsMI{a5lDfo8hU~gVCYBt(W`1jU%B-MIc|`CbCkX#mz`e{R z1MWQ71z}E;qc#PM96adX28BjMAX?LHn6%(Rl2I4nLdguO8o0t_O%1V{K?xB!xUht< z)mJRu6&4O6T{4nCSh$o!&|?~U@Y$b7k&6d{LK2YVWZ|*~ybb0RCs4?47bB~F2pNT# z&MHVg)e7_KHq=V7jgg%e!D=De(!G+AAhYO{)xssmAf~v$g-A{`z^w>Uj*}4d=mizT z_d@MK7o*xerUT}TP$4xW!-WSp_X8P)@J~bmPymz;577<^5J?Ws0Ch5s9erdS01%62 zDF}XRFdiVIZ8GpTRH&fOj4Y7KKIyW!+epcyW=`!|5FZVMM$qxwGpn}4Ldj`GC@UKT zo&5~5r0c4jB9it(Py!RA0xMv*>(5krlRs!)su6UV3dv5tP;12LC2Dl@pvM7n0S2rbllbxoda3BbhNe~IiB*>=7Y#=G2CL8$i%hJLy##3Y& zS;3V7d!6hBgc3?&;&0>gCQD($0H6s4=M)2p9|M0f5VRLeVIWHuT@q#hYzI4N;VtHk z)Y8P&zzUKDLcux9YhVw7w26QXh!=w`{9qA-ke;>|1(R)Ko`9h2QVv8e?~_OAhO_CDEZzF(?Vg=5L`{F;Z<`rQMo0OO=s1Em3R z`!@%G0jb6Ki-T2F1e?tEYn~9;2&B<~H;+6yj~wm+dgO)`Yy{R5p$w2003@3#Wq`a9 zncUlr(gRJX0a*0+fQV2i8+jx0Jj%e{MwDZK&GUdhfnTDb6Q{)9=$)%jpjKy>&`}8s zFQbL}zquc%umTC4z5Dmn3|&>$r=D=j~KKF0saT9yL`aRuB;VySW5$-XWI!r zcguvZAApIsnYkd^YT1#IVxSjW1G9;_S>9@9SN>!Wc*3icqqb21ZZ9!420JgXo|#KU z{ZX5k-ue`ao3UU@^qDr8jZ2wS8@N4MVJAW#*VG5?LVq^*sC*A8g&lNZ^kLlY2f z$6UJWA@nB(dg#r*6O6bj=F_pymS~v|Y2@*jLTDI-=7&OUp8^!X#RpMcd3?I6`ewm1 zT+;KIkQc(EgB~OSL2t~vWbs+2zZTXE-4s27 zWC7Q@Z#&VolI)7db1!b+}uz_yDBBDsb+SfGt8p=Mi00 z)GTz};}3pbNJ3J&Ph32=1LE-ZApF64k2RF?v~KWfl|$ z$PeTe(&z{$$0->(1tk^LOKKWgI(i03V-N&EVTF_+DDeoN0475=jwSwJ0_%fAiEK%S zli5Ly~Eu{cCkCi<4(~ly=(|<4*;lcII-Fn7etFB98j+GTp>XEv*!=w*<|n zRnB**#WviGcq6H6C_UQjxAbMOYmT5^9Z;-kg=XWiUQ3rdP8uy;?X1uZuueiInLN6R zThtB?th;j%aZjCCs^GwKrp#$`*ZTd-u>IPh@yE=?rTa#&R^{1*q$QMZAIbSvkNWmf zU-F6D`3C77et#Eh)b{%cw}+!nug;B0gr8kwJWLlo$5c%{m)qD|6Ek#L;;au$zUp|^ zRjqU;Nox1!$2W*w;#oO6lPeQ7#MOqJ5nlhm5drrX!BRY{@f~t2&MspX^a;4!;kt?O z8JU)FqsnKBLBWZcg2rF(uJ+?ItB8Zg5tsp*A833{vZ%q0*E_oF z&*r(+O@DJd4_hw z#2pRU+u^5O*x3JEzO^1+Uy3@tTajdq8sip!yYRfrihXp7SP#*Z?GB36K6Iyhe}MBy zMFn+sm?GL2MhHOT3D%CT@+;$OzAr4VPVJ;w2j|4(>^!ggG`Ggh`^`|^ohIKQ&v*4e zl4*{qH331N9Zj4Z(E7t}lNx=`Z;Mq}+f%3egA;XiG50~)rRFZ{F#mCpSFVf$>iI#U z^#)BHwY|eF^D%}Q!(xQ}TLb;uJ>$%m-zXQXwC?;#!%ukR_t=KGEm?PdM)uKFb34^%jofS19R1Z+ zaQ`F08xPQtYlBH2F>8^gV)t{mOTyze(}VM83#3%PoyG@cV^*?&EZOLG( ziu)n84z)X}oJR@On2`Dnj`ZU?@^Y&9PNb8^i@I(ILh*7PHlNdeT6TeF_Dw^fcpb-- zke!4?yMu%Ii9u|;P-G0Pz(IVZ&)erdnUkxkbhNp<{W6$l-uOT1{AP=Gg_nEMeZs2( zVUlgt4|ZKe3a)DzcqG@gRAO)TmHDM;AfC`%d?n!Q9Tweb`iP;+#}wsj$36J)aZty| z`MSujf)|@+r#zH82@{z=pD893l}>xh>pI`K^SnhCg5*paGkfMoYI2BL=c5`;2IsR( z17obKe5$9ST{zTN<;JL>_4&22-Oa5^5~z!&Y9H5a>T=@7-4k=xZO*+5-ug%^pW|ZA zSrT^J9a1N)-&fupS0??yD{t@qxoBN6EoQyBQ&(v{s_4J{btwt2uY}rNp5F<`F(q7i zJA;q!i63hjo3;zMbi$^gqS4XbK2J1;t7&-|f6&^BG^b2jBI1dJErluPY>Ca;aX*ny z0!a2Bj0WKSR7Dn>oVa(rK@Y+>D+4Dt=VwBqX8Z+p2?DM8Mzg-oqKk;I=L2Ib(nBZ% z9Ikn0csVxHyC**UvV!<%aeCpqH}GO+&w{#|ptt!_e(UbvekwBz1!Zl$_BAb3TtCm7 z)${V&z_`yAe&?s0j>PG79p6_Hsde(BG1WfQ)NKoMUSjKfOte=>IRv>rUZ5G}!kwuc zx{SB$&Ta3c+IIaMn*0@bd-QxxkMnI8)|e~v_N)}uloVWeTGvpuKiFdZ=Ly0@|DYpa}{U*?(M1Y zyrs!~>zbtw;?3(N*D+tBxvmaEa-8O={`}pFDcALPNXCX<83msBo$hYzz^VGz6H9(E z1ARAB&f+IL!Urh-v;Lsffp(3zd{!mYS9nCQ>vz6v8wZZ1NgEurW?#B7es|v$Sqq;x zw`rh9ie394{(Vrm;Lx;P0WoojYPh~UB(*gw;ba@we+b=k>eqo5U0$~V!zMvay8EKDMw zvy1}2&mRDtTNkNR9PG+CAijTpKP+1A;Psp9BPpfwIMTnsQCXdoa3eWe{a#I@ADT68#%bX2>$0x zV5ksO9n4P^I;7_PGFL|Q;w>W|s`XYI*_DlrGNr{E3e#Wn^~CkWgH~HMK9Z)YDu*py za#p@oS$TO5R8`I?%nb)5BlTpr0bAH;Ln&%F1>NM{BX*-|P`mti@zNzsOG%%4ofYM@X$ zhXFeNlONcQJ$O`5y7O!ZXYeX%^ZV+~7V!(s>q^q@R`}XRC2#C$rV#vMcmBv^(%S2% zXId^_Ew>IH8`^eU!I6mA&XnE_8IRqmo#u)|Ih8F0!htzQUWJ3W4Eb-Hi#eo4Qc$DA zlNi!YuyW;vbM-%ZDjm5CyEPlXxQ4_K|y9JN>|NKc2&7 z_Sut(* zt&tnCwlq)~U4P6=i|CB<_I8uJhiBZ^kOyFdqOi2o2)$!prgfsQ)x5ZaIYewW(VT#=gIw^MAi+v~RczSTDn=}id$9Up1 zla*PrHr22+dMnAgq9X|=*N;<=FYwR~+m!jxK~4(gK55qasKwnhfgaREv-Vu@PD`ex z2Rajt&J4GHsXbE5h3E1!?!wM<)`k zu3B`5c^1qZw^Y09Q1sF7Bvbco;+E}!*L7KE3gYgJk?2erMkP zI;m!nHx|TMxYFiwckaiIvqQ?A%AM-fAFII}_~XHAZZ#Xb=|LoOx!{08vz%SAo(o^# zq*c4exue}@T3UMGO|u?S4_;_lhjV`H*%?liQF$4WGGVS?<8S1!z&Z$SBvqf#=#-J=`R_X1Gmi{D%)rBsh?sNq*u3{x6=53B0tuMY8-M|;z9p;q){h7a&6 zU%axiFnaQ6%}l4IUip`Lq4@T9X}ljE^~dHUc?AUj{QvH7|wgny<86=)KsBtuUFEhoI)R=Np>mm*?-;Z_l~6ka|eFW2DjN z&3;Wv+&SdvJhF9XF}O3W(%Q=UdGoVI)XvX^iHI*Bx9e(m?t7lqdF`#Z6}trf94x2c z1uldu^||2Zq|ScbwH^o}DUXd-7BAp~zV|D1?NuYSqx)2P+Fi$1U0VVJEb5D`Q1@3e z#zQ>A!B;NiYZ1J>w0$6+IkOxS(^+t}Y8=KBmJ%+kuY}E1SLn5xZ*0cAj(PIl8QXDx zlk=gO40}LdY>2ILjl-F)yV_w>4e|lkR{6P=%-*M3CsN&yKcptFzjawk^|-f~q-1~q zOLXXf(Y^lYZOhxdsO63Nje2#z?U#6ty7TIcAH7H@=u@QkSizv4=QsoI)v~v}0~}W4GJ)TvKX8Y*4F0>mqXU z2^a6k+Z(*fb+c@1E2eH6{-3f^_5D9|-rw-xUG?(PytCyNIN(uSbV+z(e0_HVwcWI7 zpQNRAs18*zwzf!I+?I*&{8~HJP`Pz%V_ljJ*GSrEiB_&WZHPN@0PpSar$J~Ey_C6q(Rq&X|Od2$5 a6v#clA4L35zdJgAkN6k{4T$9*<-Y*ZXBEW& literal 0 HcmV?d00001 diff --git a/HandheldCompanion/Managers/GamepadFocusManager.cs b/HandheldCompanion/UI/UIGamepad.cs similarity index 94% rename from HandheldCompanion/Managers/GamepadFocusManager.cs rename to HandheldCompanion/UI/UIGamepad.cs index 6fad8e708..dd2edfc76 100644 --- a/HandheldCompanion/Managers/GamepadFocusManager.cs +++ b/HandheldCompanion/UI/UIGamepad.cs @@ -1,6 +1,7 @@ using GregsStack.InputSimulatorStandard.Native; using HandheldCompanion.Controllers; using HandheldCompanion.Inputs; +using HandheldCompanion.UI; using HandheldCompanion.Utils; using HandheldCompanion.Views; using HandheldCompanion.Views.Classes; @@ -22,7 +23,7 @@ namespace HandheldCompanion.Managers { - public class GamepadFocusManager + public class UIGamepad { #region events public static event GotFocusEventHandler GotFocus; @@ -50,7 +51,7 @@ public class GamepadFocusManager // key: Page private ConcurrentDictionary prevControl = new(); - public GamepadFocusManager(GamepadWindow gamepadWindow, Frame contentFrame) + public UIGamepad(GamepadWindow gamepadWindow, Frame contentFrame) { // set current window _currentWindow = gamepadWindow; @@ -62,6 +63,8 @@ public GamepadFocusManager(GamepadWindow gamepadWindow, Frame contentFrame) _currentWindow.GotGamepadWindowFocus += _currentWindow_GotGamepadWindowFocus; _currentWindow.LostGamepadWindowFocus += _currentWindow_LostGamepadWindowFocus; + _currentWindow.ContentDialogOpened += _currentWindow_ContentDialogOpened; + _currentWindow.ContentDialogClosed += _currentWindow_ContentDialogClosed; _currentWindow.Activated += (sender, e) => _currentWindow_GotFocus(sender, null); _currentWindow.Deactivated += (sender, e) => _currentWindow_LostFocus(sender, null); @@ -86,6 +89,18 @@ public GamepadFocusManager(GamepadWindow gamepadWindow, Frame contentFrame) _gamepadTimer.Elapsed += _gamepadFrame_PageRendered; } + private void _currentWindow_ContentDialogClosed() + { + if (prevControl.TryGetValue(_gamepadPage.Tag, out Control control)) + Focus(control); + } + + private void _currentWindow_ContentDialogOpened() + { + Control control = _currentWindow.controlElements.OfType