From b1120c8d49cc9f93b63a69c6ac439b52d574d67a Mon Sep 17 00:00:00 2001 From: Vincent Maas Date: Sun, 11 Feb 2024 14:38:53 +0100 Subject: [PATCH] add creation of SDR color profile, add NVIDIA HDR10+ support, fix MinTML/MaxTML, add LG TPC/GSR actions, add LG/Samsung trigger events --- ColorControl/ColorControl.csproj | 4 +- ColorControl/MainForm.Designer.cs | 14 +- ColorControl/MainForm.cs | 45 ++- ColorControl/Program.cs | 1 + ColorControl/Services/AMD/AmdPanel.cs | 4 +- ColorControl/Services/Common/PresetBase.cs | 28 +- ColorControl/Services/Common/ServiceBase.cs | 59 ++- .../Services/Common/ServiceFormUtils.cs | 3 +- .../EventDispatcher/EventDispatcher.cs | 2 + .../EventDispatcher/PowerEventDispatcher.cs | 43 ++- .../EventDispatcher/ProcessEventDispatcher.cs | 16 +- ColorControl/Services/LG/LgDevice.cs | 31 ++ ColorControl/Services/LG/LgPanel.cs | 30 +- ColorControl/Services/LG/LgService.cs | 180 +++++---- .../Services/NVIDIA/NvPanel.Designer.cs | 72 +++- ColorControl/Services/NVIDIA/NvPanel.cs | 83 ++++- ColorControl/Services/NVIDIA/NvPreset.cs | 8 +- ColorControl/Services/NVIDIA/NvService.cs | 345 ++++++++++++++---- ColorControl/Services/Samsung/SamsungPanel.cs | 32 +- .../Services/Samsung/SamsungService.cs | 178 +++++---- ColorControl/XForms/ColorProfileViewModel.cs | 50 ++- ColorControl/XForms/ColorProfileWindow.xaml | 61 ++-- .../XForms/ColorProfileWindow.xaml.cs | 99 ++++- ColorControl/lgtv/LgTvApi.cs | 16 +- MHC2Gen/IccContext.cs | 49 ++- MHC2Gen/MHC2Wrapper.cs | 45 +++ MHC2Gen/ST2084.cs | 1 + .../Native/Attributes/FunctionIdAttribute.cs | 6 +- NvAPIWrapper/Native/GeneralApi.cs | 2 +- .../Native/Helpers/DelegateFactory.cs | 8 +- NvAPIWrapper/Native/Helpers/FunctionId.cs | 8 +- Shared/Common/Utils.cs | 4 +- Shared/Contracts/Config.cs | 1 + Shared/Contracts/NVIDIA/NvHdrSettings.cs | 32 ++ Shared/Forms/MessageForms.cs | 5 + Shared/Native/CCD.cs | 69 +++- Shared/Shared.csproj | 1 + novideo_srgb/MainWindow.xaml.cs | 2 +- novideo_srgb/MonitorData.cs | 5 + 39 files changed, 1308 insertions(+), 334 deletions(-) create mode 100644 Shared/Contracts/NVIDIA/NvHdrSettings.cs diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index 2c33512..a0d7a3b 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -18,8 +18,8 @@ Maassoft Maassoft 0 - 9.8.0.1 - 9.8.0.1 + 9.8.1.0 + 9.8.1.0 false true false diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs index 3ce3d9a..68fa5aa 100644 --- a/ColorControl/MainForm.Designer.cs +++ b/ColorControl/MainForm.Designer.cs @@ -64,6 +64,7 @@ private void InitializeComponent() btnUpdate = new System.Windows.Forms.Button(); mnuColorProfiles = new System.Windows.Forms.ContextMenuStrip(components); miCreateHDRColorProfile = new System.Windows.Forms.ToolStripMenuItem(); + miCreateSDRColorProfile = new System.Windows.Forms.ToolStripMenuItem(); tcMain.SuspendLayout(); tabOptions.SuspendLayout(); grpOptionsModules.SuspendLayout(); @@ -475,9 +476,9 @@ private void InitializeComponent() // mnuColorProfiles // mnuColorProfiles.ImageScalingSize = new System.Drawing.Size(20, 20); - mnuColorProfiles.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miCreateHDRColorProfile }); + mnuColorProfiles.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miCreateHDRColorProfile, miCreateSDRColorProfile }); mnuColorProfiles.Name = "mnuLgButtons"; - mnuColorProfiles.Size = new System.Drawing.Size(214, 26); + mnuColorProfiles.Size = new System.Drawing.Size(214, 70); // // miCreateHDRColorProfile // @@ -486,6 +487,13 @@ private void InitializeComponent() miCreateHDRColorProfile.Text = "Create HDR Color Profile..."; miCreateHDRColorProfile.Click += miCreateHDRColorProfile_Click; // + // miCreateSDRColorProfile + // + miCreateSDRColorProfile.Name = "miCreateSDRColorProfile"; + miCreateSDRColorProfile.Size = new System.Drawing.Size(213, 22); + miCreateSDRColorProfile.Text = "Create SDR Color Profile..."; + miCreateSDRColorProfile.Click += miCreateSDRColorProfile_Click; + // // MainForm // AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -503,7 +511,6 @@ private void InitializeComponent() Deactivate += MainForm_Deactivate; FormClosing += MainForm_FormClosing; FormClosed += MainForm_FormClosed; - Load += MainForm_Load; Shown += MainForm_Shown; ResizeBegin += MainForm_ResizeBegin; ResizeEnd += MainForm_ResizeEnd; @@ -557,6 +564,7 @@ private void InitializeComponent() private System.Windows.Forms.Button btnOptionsColorProfiles; private System.Windows.Forms.ContextMenuStrip mnuColorProfiles; private System.Windows.Forms.ToolStripMenuItem miCreateHDRColorProfile; + private System.Windows.Forms.ToolStripMenuItem miCreateSDRColorProfile; } } diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index 6d97155..55eeb5c 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -168,7 +168,7 @@ public MainForm(AppContextProvider appContextProvider, PowerEventDispatcher powe _initialized = true; - AfterInitialized(); + Task.Run(AfterInitialized); if (_config.UseDarkMode) { @@ -176,10 +176,6 @@ public MainForm(AppContextProvider appContextProvider, PowerEventDispatcher powe } } - private void MainForm_Load(object sender, EventArgs e) - { - } - private void LoadModules() { _modules.Add("NVIDIA controller", InitNvService); @@ -236,6 +232,8 @@ private void InitModules() } tcMain.SelectedIndex = 0; + + _serviceManager.NvService?.InstallEventHandlers(); } private void UpdateServiceInfo() @@ -352,7 +350,7 @@ private UserControl InitLgService() { _serviceManager.LgService = _serviceProvider.GetRequiredService(); - _lgPanel = new LgPanel(_serviceManager.LgService, _serviceManager.NvService, _serviceManager.AmdService, _trayIcon, Handle); + _lgPanel = new LgPanel(_serviceManager.LgService, _serviceManager.NvService, _serviceManager.AmdService, _trayIcon, Handle, _powerEventDispatcher); _lgPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; _lgPanel.Init(); @@ -378,7 +376,7 @@ private UserControl InitSamsungService() //_serviceManager.SamsungService.Init(); - _samsungPanel = new SamsungPanel(_serviceManager.SamsungService, _serviceManager.NvService, _serviceManager.AmdService, Handle); + _samsungPanel = new SamsungPanel(_serviceManager.SamsungService, _serviceManager.NvService, _serviceManager.AmdService, Handle, _powerEventDispatcher); _samsungPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; _samsungPanel.Init(); @@ -958,13 +956,21 @@ private void MainForm_Activated(object sender, EventArgs e) } } - private void AfterInitialized() + private async Task AfterInitialized() { - _nvPanel?.AfterInitialized(); - _amdPanel?.AfterInitialized(); + await _powerEventDispatcher.SendEventAsync(PowerEventDispatcher.Event_Startup); + + if (_nvPanel != null) + { + await _nvPanel.AfterInitialized(); + } + if (_amdPanel != null) + { + await _amdPanel.AfterInitialized(); + } if (_trayIcon.Visible) { - var _ = CheckForUpdates(); + await CheckForUpdates(); } } @@ -1289,7 +1295,7 @@ private async void MainForm_Click(object sender, EventArgs e) //InstallUpdate(""); //await Test(); - //_serviceManager.NvService.TestResolution(); + _serviceManager.NvService?.TestResolution(); } private async Task Test() @@ -1395,8 +1401,15 @@ private void btnOptionsAdvanced_Click(object sender, EventArgs e) SubLabel = "This enables shortcuts to work during applications/games that block certain keys (like WinKey or Control). NOTE: if the application in the foreground runs with higher privileges than ColorControl, Raw Input does not work and normal hot keys are used", Value = _config.UseRawInput }; + var setMinTmlAndMaxTmlField = new FieldDefinition + { + FieldType = FieldType.CheckBox, + Label = "Set MinTML and MaxTML when applying color profiles", + SubLabel = "When this is enabled MinTML and MaxTML will be automatically be set to respectively the minimum luminance and the maximum luminance of the color profile", + Value = _config.SetMinTmlAndMaxTml + }; - var values = MessageForms.ShowDialog("Advanced settings", new[] { processPollingIntervalField, useRawInputField }); + var values = MessageForms.ShowDialog("Advanced settings", new[] { processPollingIntervalField, useRawInputField, setMinTmlAndMaxTmlField }); if (values?.Any() != true) { @@ -1405,6 +1418,7 @@ private void btnOptionsAdvanced_Click(object sender, EventArgs e) _config.ProcessMonitorPollingInterval = processPollingIntervalField.ValueAsInt; _config.UseRawInput = useRawInputField.ValueAsBool; + _config.SetMinTmlAndMaxTml = setMinTmlAndMaxTmlField.ValueAsBool; KeyboardShortcutManager.SetUseRawInput(_config.UseRawInput); } @@ -1441,5 +1455,10 @@ private void btnOptionsColorProfiles_Click(object sender, EventArgs e) { mnuColorProfiles.ShowCustom(btnOptionsColorProfiles); } + + private void miCreateSDRColorProfile_Click(object sender, EventArgs e) + { + ColorProfileWindow.CreateAndShow(isHDR: false); + } } } \ No newline at end of file diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs index 6646ffa..07b10af 100644 --- a/ColorControl/Program.cs +++ b/ColorControl/Program.cs @@ -273,6 +273,7 @@ private static async Task RunService(string[] args) .ConfigureServices(services => { services.RegisterSharedServices(); + services.AddSingleton(); services.AddHostedService(); }) .Build(); diff --git a/ColorControl/Services/AMD/AmdPanel.cs b/ColorControl/Services/AMD/AmdPanel.cs index cee18e4..36819b0 100644 --- a/ColorControl/Services/AMD/AmdPanel.cs +++ b/ColorControl/Services/AMD/AmdPanel.cs @@ -120,9 +120,9 @@ private void AddOrUpdateItemAmd(AmdPreset preset = null) ServiceFormUtils.AddOrUpdateListItem(lvAmdPresets, _amdService.GetPresets(), _config, preset); } - public void AfterInitialized() + public async Task AfterInitialized() { - var _ = ApplyAmdPresetOnStartup(); + await ApplyAmdPresetOnStartup(); } private async Task ApplyAmdPresetOnStartup(int attempts = 5) diff --git a/ColorControl/Services/Common/PresetBase.cs b/ColorControl/Services/Common/PresetBase.cs index 4b86706..fdd0bb2 100644 --- a/ColorControl/Services/Common/PresetBase.cs +++ b/ColorControl/Services/Common/PresetBase.cs @@ -1,4 +1,5 @@ -using ColorControl.Shared.Common; +using ColorControl.Services.EventDispatcher; +using ColorControl.Shared.Common; using ColorControl.Shared.Contracts; using NStandard; using System; @@ -68,10 +69,10 @@ public bool TriggerActive(PresetTriggerContext context) var active = Conditions == PresetConditionType.None || (Conditions.HasFlag(PresetConditionType.SDR) ? !context.IsHDRActive : true) && (Conditions.HasFlag(PresetConditionType.HDR) ? context.IsHDRActive : true) && (Conditions.HasFlag(PresetConditionType.GsyncDisabled) ? !context.IsGsyncActive : true) && (Conditions.HasFlag(PresetConditionType.GsyncEnabled) ? context.IsGsyncActive : true); - var allProcesses = IncludedProcesses.Contains("*"); - if (Trigger == PresetTriggerType.ProcessSwitch) + if (Trigger == PresetTriggerType.ProcessSwitch && context.Triggers.Contains(Trigger)) { + var allProcesses = IncludedProcesses.Contains("*"); active = active && (allProcesses || (context.ChangedProcesses?.Any() ?? false)); if (active) @@ -88,6 +89,15 @@ public bool TriggerActive(PresetTriggerContext context) active = included && !excluded && screenSizeCheck && notificationsDisabledCheck; } } + else if ((Trigger == PresetTriggerType.ScreensaverStart || Trigger == PresetTriggerType.ScreensaverStop) && context.Triggers.Contains(Trigger)) + { + active = active && (Trigger == PresetTriggerType.ScreensaverStart && context.ScreenSaverTransitionState == ScreenSaverTransitionState.Started || + Trigger == PresetTriggerType.ScreensaverStop && context.ScreenSaverTransitionState == ScreenSaverTransitionState.Stopped); + } + else + { + active = context.Triggers.Contains(Trigger); + } return active; } @@ -99,7 +109,13 @@ public override string ToString() return string.Empty; } - return $"On {Trigger.GetDescription()} of {DisplayProcesses(IncludedProcesses)}{(ExcludedProcesses.Any() ? ", excluding " + DisplayProcesses(ExcludedProcesses) : string.Empty)}{(Conditions > 0 ? ", only in " + string.Join(", ", Utils.GetDescriptions((int)Conditions)) : string.Empty)}"; + var text = $"On {Trigger.GetDescription()}"; + + if (Trigger == PresetTriggerType.ProcessSwitch) + { + text += $"of {DisplayProcesses(IncludedProcesses)}{(ExcludedProcesses.Any() ? ", excluding " + DisplayProcesses(ExcludedProcesses) : string.Empty)}"; + } + return text += $"{(Conditions > 0 ? ", only in " + string.Join(", ", Utils.GetDescriptions((int)Conditions)) : string.Empty)}"; } public static string DisplayProcesses(IEnumerable processes) @@ -110,12 +126,14 @@ public static string DisplayProcesses(IEnumerable processes) class PresetTriggerContext { + public IEnumerable Triggers { get; set; } = new[] { PresetTriggerType.ProcessSwitch }; public bool IsHDRActive { get; set; } public bool IsGsyncActive { get; set; } public Process ForegroundProcess { get; set; } public bool ForegroundProcessIsFullScreen { get; set; } public List ChangedProcesses { get; set; } public bool IsNotificationDisabled { get; set; } + public ScreenSaverTransitionState ScreenSaverTransitionState { get; set; } } internal abstract class PresetBase @@ -177,7 +195,7 @@ public virtual string GetTextForMenuItem() return name; } - public void UpdateTrigger(PresetTriggerType triggerType, PresetConditionType conditions, string includedProcesses, string excludedProcesses) + public void UpdateTrigger(PresetTriggerType triggerType, PresetConditionType conditions, string includedProcesses = null, string excludedProcesses = null) { if (!Triggers.Any()) { diff --git a/ColorControl/Services/Common/ServiceBase.cs b/ColorControl/Services/Common/ServiceBase.cs index 5c96f33..d790320 100644 --- a/ColorControl/Services/Common/ServiceBase.cs +++ b/ColorControl/Services/Common/ServiceBase.cs @@ -1,7 +1,10 @@ -using ColorControl.Shared.Services; +using ColorControl.Services.EventDispatcher; +using ColorControl.Shared.Native; +using ColorControl.Shared.Services; using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -27,6 +30,7 @@ public abstract string ServiceName protected List _presets; protected T _lastAppliedPreset; protected string _loadPresetsError; + protected T _lastTriggeredPreset; protected AppContextProvider _appContextProvider; @@ -176,5 +180,58 @@ public void ToggleQuickAccessForm() QuickAccessForm.ToggleQuickAccessForm(this); } + protected async Task CreateTriggerContext(ServiceManager serviceManager, ProcessChangedEventArgs context = null, bool? isHDRActive = null, IList triggerTypes = null) + { + triggerTypes ??= new[] { PresetTriggerType.ProcessSwitch }; + + var changedProcesses = new List(); + if (context?.ForegroundProcess != null) + { + changedProcesses.Add(context.ForegroundProcess); + } + + var isGsyncActive = await serviceManager.HandleExternalServiceAsync("GsyncEnabled", new[] { "" }); + + var triggerContext = new PresetTriggerContext + { + Triggers = triggerTypes, + IsHDRActive = isHDRActive ?? CCD.IsHDREnabled(), + IsGsyncActive = isGsyncActive, + ForegroundProcess = context?.ForegroundProcess, + ForegroundProcessIsFullScreen = context?.ForegroundProcessIsFullScreen ?? false, + IsNotificationDisabled = context?.IsNotificationDisabled ?? false, + ChangedProcesses = changedProcesses, + ScreenSaverTransitionState = context?.ScreenSaverTransitionState ?? ScreenSaverTransitionState.None + }; + + return triggerContext; + } + + protected async Task ExecuteScreenSaverPresets(ServiceManager serviceManager, ProcessChangedEventArgs context, bool? isHDRActive = null) + { + await ExecuteEventPresets(serviceManager, new[] { PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop }, context, isHDRActive); + } + + public async Task ExecuteEventPresets(ServiceManager serviceManager, IList triggerTypes, ProcessChangedEventArgs context = null, bool? isHDRActive = null) + { + var triggerContext = await CreateTriggerContext(serviceManager, context, isHDRActive, triggerTypes); + + var triggerPresets = _presets.Where(p => p.Triggers.Any(t => t.TriggerActive(triggerContext))).ToList(); + + if (!triggerPresets.Any()) + { + return; + } + + Logger.Debug($"Executing event presets count: {triggerPresets.Count}"); + + foreach (var preset in triggerPresets) + { + Logger.Debug($"Executing event preset: {preset.name}"); + + await ApplyPreset(preset); + } + } + } } diff --git a/ColorControl/Services/Common/ServiceFormUtils.cs b/ColorControl/Services/Common/ServiceFormUtils.cs index a48025c..398d7cf 100644 --- a/ColorControl/Services/Common/ServiceFormUtils.cs +++ b/ColorControl/Services/Common/ServiceFormUtils.cs @@ -1,6 +1,5 @@ using ColorControl.Services.Common; using ColorControl.Shared.Contracts; -using ColorControl.Shared.Forms; using System; using System.Collections.Generic; using System.Linq; @@ -101,7 +100,7 @@ public static void ListViewItemChecked(ListView listView, ItemCheckedEventArg { menu.DropDownItems.Clear(); - foreach (var nvPreset in service?.GetPresets() ?? new List()) + foreach (var nvPreset in service?.GetPresets()?.OrderBy(p => p.name).ToList() ?? new List()) { var text = nvPreset.name; diff --git a/ColorControl/Services/EventDispatcher/EventDispatcher.cs b/ColorControl/Services/EventDispatcher/EventDispatcher.cs index 38b1a07..08fae03 100644 --- a/ColorControl/Services/EventDispatcher/EventDispatcher.cs +++ b/ColorControl/Services/EventDispatcher/EventDispatcher.cs @@ -53,6 +53,8 @@ public async Task DispatchEventAsync(string eventName, T eventArgs) { await asyncEventHandlers[eventName]?.InvokeAsync(this, eventArgs); } + + DispatchEvent(eventName, eventArgs); } protected bool HasHandlers(string eventName) diff --git a/ColorControl/Services/EventDispatcher/PowerEventDispatcher.cs b/ColorControl/Services/EventDispatcher/PowerEventDispatcher.cs index 7823315..11ca589 100644 --- a/ColorControl/Services/EventDispatcher/PowerEventDispatcher.cs +++ b/ColorControl/Services/EventDispatcher/PowerEventDispatcher.cs @@ -1,5 +1,7 @@ using Microsoft.Win32; +using NWin32; using System; +using System.Threading.Tasks; namespace ColorControl.Services.EventDispatcher { @@ -36,10 +38,13 @@ public class PowerEventDispatcher : EventDispatcher { public const string Event_Suspend = "Suspend"; public const string Event_Resume = "Resume"; + public const string Event_Startup = "Startup"; public const string Event_Shutdown = "Shutdown"; public const string Event_MonitorOff = "MonitorOff"; public const string Event_MonitorOn = "MonitorOn"; + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + public PowerEventDispatcher() { SystemEvents.PowerModeChanged += OnPowerModeChanged; @@ -50,6 +55,7 @@ public void SendEvent(string eventName) var state = eventName switch { Event_Shutdown => PowerOnOffState.ShutDown, + Event_Startup => PowerOnOffState.StartUp, Event_MonitorOff => PowerOnOffState.MonitorOff, Event_MonitorOn => PowerOnOffState.MonitorOn, _ => PowerOnOffState.None @@ -60,19 +66,52 @@ public void SendEvent(string eventName) return; } + if (state == PowerOnOffState.ShutDown) + { + DispatchEventWithExecutionState(Event_Shutdown, PowerOnOffState.ShutDown); + return; + } + DispatchEvent(eventName, new PowerStateChangedEventArgs(state)); } - private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e) + public async Task SendEventAsync(string eventName) + { + var state = eventName switch + { + Event_Startup => PowerOnOffState.StartUp, + _ => PowerOnOffState.None + }; + + await DispatchEventAsync(eventName, new PowerStateChangedEventArgs(state)); + } + + private async void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e) { if (e.Mode == PowerModes.Suspend) { - DispatchEvent(Event_Suspend, new PowerStateChangedEventArgs(PowerOnOffState.StandBy)); + DispatchEventWithExecutionState(Event_Suspend, PowerOnOffState.StandBy); } else if (e.Mode == PowerModes.Resume) { + await DispatchEventAsync(Event_Resume, new PowerStateChangedEventArgs(PowerOnOffState.Resume)); DispatchEvent(Event_Resume, new PowerStateChangedEventArgs(PowerOnOffState.Resume)); } } + + private void DispatchEventWithExecutionState(string eventName, PowerOnOffState powerState) + { + var error = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED); + try + { + Logger.Debug($"SetThreadExecutionState: {error}, Thread#: {Environment.CurrentManagedThreadId}"); + DispatchEvent(eventName, new PowerStateChangedEventArgs(powerState)); + } + finally + { + var error2 = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS); + Logger.Debug($"SetThreadExecutionState (reset): {error2}, Thread#: {Environment.CurrentManagedThreadId}"); + } + } } } diff --git a/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs b/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs index 8069665..4ffc2b5 100644 --- a/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs +++ b/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs @@ -10,6 +10,14 @@ namespace ColorControl.Services.EventDispatcher { + public enum ScreenSaverTransitionState + { + None = 0, + Started = 1, + Running = 2, + Stopped = 3 + } + public class ProcessChangedEventArgs : EventArgs { public IList StartedProcesses { get; set; } @@ -21,6 +29,7 @@ public class ProcessChangedEventArgs : EventArgs public string LastFullScreenProcessName = string.Empty; public bool StoppedFullScreen { get; set; } public bool IsScreenSaverActive { get; set; } + public ScreenSaverTransitionState ScreenSaverTransitionState { get; set; } } @@ -100,7 +109,12 @@ private void FillContext(ProcessChangedEventArgs context) { var process = context.RunningProcesses.FirstOrDefault(p => p.Id == processId); - context.IsScreenSaverActive = process?.ProcessName?.Contains(".scr") == true; + var screenSaverActive = process?.ProcessName?.Contains(".scr") == true; + + context.IsScreenSaverActive = screenSaverActive; + context.ScreenSaverTransitionState = screenSaverActive ? + context.ScreenSaverTransitionState == ScreenSaverTransitionState.None ? ScreenSaverTransitionState.Started : ScreenSaverTransitionState.Running : + context.ScreenSaverTransitionState == ScreenSaverTransitionState.Running ? ScreenSaverTransitionState.Stopped : ScreenSaverTransitionState.None; context.ForegroundProcess = process; context.ForegroundProcessIsFullScreen = isFullScreen; diff --git a/ColorControl/Services/LG/LgDevice.cs b/ColorControl/Services/LG/LgDevice.cs index e7a846a..594c8bf 100644 --- a/ColorControl/Services/LG/LgDevice.cs +++ b/ColorControl/Services/LG/LgDevice.cs @@ -272,6 +272,8 @@ public LgDevice(string name, string ipAddress, string macAddress, bool isCustom AddSetConfigAction("tv.conti.supportUsedTime", typeof(BoolFalseToTrue), title: "Total Power On Time"); AddGenericPictureAction("wolwowlOnOff", typeof(FalseToTrue), category: "network", title: "Wake-On-LAN"); + AddLunaAction("TPC", typeof(BoolFalseToTrue), title: "Temporal Peak Luminance Control (TPC)", ModelYear.Series2020); + AddLunaAction("GSR", typeof(BoolFalseToTrue), title: "Global Sticky/Stress Reduction (GSR)", ModelYear.Series2020); } ~LgDevice() @@ -360,6 +362,22 @@ private void AddSetConfigAction(string name, Type type, string title) _invokableActions.Add(action); } + private void AddLunaAction(string name, Type type, string title, ModelYear fromModelYear = ModelYear.None) + { + var action = new InvokableAction + { + Name = name, + AsyncFunction = GenericLunaAction, + EnumType = type, + Title = title == null ? Utils.FirstCharUpperCase(name) : title, + Category = "Config", + FromModelYear = fromModelYear, + Advanced = true + }; + + _invokableActions.Add(action); + } + public void AddGameBarAction(string name) { if (!ActionsOnGameBar.Contains(name)) @@ -1145,6 +1163,19 @@ private async Task GenericSetConfigAction(Dictionary param return true; } + private async Task GenericLunaAction(Dictionary parameters) + { + await CheckConnectionAsync(); + + var key = parameters["name"].ToString(); + var values = parameters["value"] as object[]; + var value = values[0]; + + await _lgTvApi.SetTpcOrGsr(key, value?.ToString() == "bool_true"); + + return true; + } + private void UpdateCurrentValueOfAction(string settingName, string value) { if (settingName == "backlight" || settingName == "contrast" || settingName == "brightness" || settingName == "color") diff --git a/ColorControl/Services/LG/LgPanel.cs b/ColorControl/Services/LG/LgPanel.cs index 1e58a11..77b9c5c 100644 --- a/ColorControl/Services/LG/LgPanel.cs +++ b/ColorControl/Services/LG/LgPanel.cs @@ -1,5 +1,6 @@ using ColorControl.Services.AMD; using ColorControl.Services.Common; +using ColorControl.Services.EventDispatcher; using ColorControl.Services.NVIDIA; using ColorControl.Shared.Common; using ColorControl.Shared.Contracts; @@ -12,6 +13,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -28,6 +30,7 @@ public partial class LgPanel : UserControl, IModulePanel private LgService _lgService; private NvService _nvService; private AmdService _amdService; + private readonly PowerEventDispatcher _powerEventDispatcher; private IntPtr _mainHandle; private NotifyIcon _trayIcon; @@ -35,13 +38,14 @@ public partial class LgPanel : UserControl, IModulePanel private bool _disableEvents = false; private LgGameBar _gameBarForm; - internal LgPanel(LgService lgService, NvService nvService, AmdService amdService, NotifyIcon trayIcon, IntPtr handle) + internal LgPanel(LgService lgService, NvService nvService, AmdService amdService, NotifyIcon trayIcon, IntPtr handle, PowerEventDispatcher powerEventDispatcher) { _lgService = lgService; _nvService = nvService; _amdService = amdService; _trayIcon = trayIcon; _mainHandle = handle; + _powerEventDispatcher = powerEventDispatcher; _config = Shared.Common.GlobalContext.CurrentContext.Config; @@ -72,10 +76,17 @@ internal LgPanel(LgService lgService, NvService nvService, AmdService amdService public void Init() { - _lgService.RefreshDevices(afterStartUp: true).ContinueWith((_) => FormUtils.BeginInvokeCheck(this, () => AfterLgServiceRefreshDevices())); + var _ = Handle; + + _powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Startup, PowerStateChanged); _lgService.InstallEventHandlers(); } + private async Task PowerStateChanged(object sender, PowerStateChangedEventArgs e, CancellationToken token) + { + await _lgService.RefreshDevices(afterStartUp: true).ContinueWith((_) => FormUtils.BeginInvokeCheck(this, AfterLgServiceRefreshDevices)); + } + private void _lgService_SelectedDeviceChangedEvent(object sender, EventArgs e) { FormUtils.BeginInvokeCheck(this, () => SetLgDevicesSelectedIndex(sender)); @@ -92,7 +103,7 @@ private void SetLgDevicesSelectedIndex(object sender) cbxLgDevices.SelectedIndex = cbxLgDevices.Items.IndexOf(sender); } - private void AfterLgServiceRefreshDevices() + private async Task AfterLgServiceRefreshDevices() { FillLgDevices(); @@ -100,7 +111,14 @@ private void AfterLgServiceRefreshDevices() if (startUpParams.ExecuteLgPreset) { - var _ = _lgService.ApplyPreset(startUpParams.LgPresetName); + await _lgService.ApplyPreset(startUpParams.LgPresetName); + return; + } + + + if (startUpParams.RunningFromScheduledTask) + { + await _lgService.ExecuteEventPresets(PresetTriggerType.Startup); } } @@ -320,7 +338,7 @@ public void UpdateInfo() chkLgRemoteControlShow.Checked = _lgService.Config.ShowRemoteControl; scLgController.Panel2Collapsed = !_lgService.Config.ShowRemoteControl; - FormUtils.BuildComboBox(cbxLgPresetTrigger, PresetTriggerType.Resume, PresetTriggerType.Shutdown, PresetTriggerType.Standby, PresetTriggerType.Startup, PresetTriggerType.Reserved5, PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop); + FormUtils.BuildComboBox(cbxLgPresetTrigger, PresetTriggerType.Reserved5); if (!string.IsNullOrEmpty(_lgTabMessage)) { @@ -660,7 +678,7 @@ private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e) return; } - if (e.Index is 0 or 1 or 4 or 7 && !(device.PowerOnAfterResume || device.PowerOnAfterStartup || device.PowerOnAfterScreenSaver || device.PowerOnByWindows)) + if (e.Index is 0 or 1 or 5 or 8 && !(device.PowerOnAfterResume || device.PowerOnAfterStartup || device.PowerOnAfterScreenSaver || device.PowerOnByWindows)) { MessageForms.InfoOk( @"Be sure to activate the following setting on the TV, or the app will not be able to wake the TV: diff --git a/ColorControl/Services/LG/LgService.cs b/ColorControl/Services/LG/LgService.cs index 04a9368..a79214e 100644 --- a/ColorControl/Services/LG/LgService.cs +++ b/ColorControl/Services/LG/LgService.cs @@ -59,7 +59,6 @@ public LgDevice SelectedDevice private bool _poweredOffByScreenSaver; private int _poweredOffByScreenSaverProcessId; - private LgPreset _lastTriggeredPreset; private RestartDetector _restartDetector; private readonly WinApiService _winApiService; private readonly WinApiAdminService _winApiAdminService; @@ -321,7 +320,7 @@ public async Task RefreshDevices(bool connect = true, bool afterStartUp = false) { if (_allowPowerOn) { - WakeAfterStartupOrResume(); + await WakeAfterStartupOrResume(); } else { @@ -415,7 +414,7 @@ internal bool WakeSelectedDevice() return SelectedDevice?.Wake() ?? false; } - internal void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + internal Task WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) { Devices.ForEach(d => d.ClearPowerOffTask()); @@ -423,13 +422,13 @@ internal void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.S state == PowerOnOffState.Resume && d.PowerOnAfterResume || state == PowerOnOffState.ScreenSaver && d.PowerOnAfterScreenSaver); - PowerOnDevicesTask(wakeDevices, state, checkUserSession); + return PowerOnDevicesTask(wakeDevices, state, checkUserSession); } public void InstallEventHandlers() { _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Suspend, PowerModeChanged); - _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Resume, PowerModeChanged); + _powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Resume, PowerModeResume); _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Shutdown, PowerModeChanged); _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOff, PowerModeChanged); _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOn, PowerModeChanged); @@ -462,25 +461,39 @@ private void SessionSwitched(object sender, SessionSwitchEventArgs e) } } + private async Task PowerModeResume(object sender, PowerStateChangedEventArgs e, CancellationToken _) + { + Logger.Debug($"PowerModeChanged: {e.State}"); + + await WakeAfterStartupOrResume(PowerOnOffState.Resume); + + ExecutePresetsForEvent(PresetTriggerType.Resume); + } + private void PowerModeChanged(object sender, PowerStateChangedEventArgs e) { Logger.Debug($"PowerModeChanged: {e.State}"); + if (Devices?.Any() != true) + { + Logger.Debug("Devices have not been loaded, ignoring event"); + return; + } + switch (e.State) { - case PowerOnOffState.Resume: - { - WakeAfterStartupOrResume(PowerOnOffState.Resume); - break; - } case PowerOnOffState.StandBy: { + ExecutePresetsForEvent(PresetTriggerType.Standby); + var devices = Devices.Where(d => d.PowerOffOnStandby); PowerOffDevices(devices, PowerOnOffState.StandBy); break; } case PowerOnOffState.ShutDown: { + ExecutePresetsForEvent(PresetTriggerType.Shutdown); + var devices = Devices.Where(d => d.PowerOffOnShutdown); PowerOffDevices(devices); @@ -502,6 +515,26 @@ private void PowerModeChanged(object sender, PowerStateChangedEventArgs e) } } + private void ExecutePresetsForEvent(PresetTriggerType triggerType) + { + var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == triggerType)).ToList(); + + if (!presets.Any()) + { + return; + } + + var applicableDevices = Devices.Where(d => d.TriggersEnabled && + presets.Any(p => (string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase))).ToList(); + + if (!applicableDevices.Any()) + { + return; + } + + var _ = Task.WhenAll(ExecuteEventPresets(_serviceManager, new[] { triggerType }, isHDRActive: applicableDevices.Any(d => d.IsUsingHDRPictureMode()))).ConfigureAwait(true); + } + private void SessionEnded(object sender, SessionEndedEventArgs e) { //if (e.Reason == SessionEndReasons.SystemShutdown) @@ -535,10 +568,15 @@ public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, Ca { try { - var wasConnected = Devices.Any(d => (d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver) && d.IsConnected()); + var wasConnected = Devices?.Any(d => (d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver) && d.IsConnected()); + + if (!wasConnected.HasValue) + { + return; + } var applicableDevices = Devices.Where(d => d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver || d.TriggersEnabled && _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None) && - ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))); + ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))).ToList(); if (!applicableDevices.Any()) { @@ -573,7 +611,9 @@ public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, Ca _poweredOffByScreenSaver = false; _poweredOffByScreenSaverProcessId = 0; - WakeAfterStartupOrResume(PowerOnOffState.ScreenSaver); + await WakeAfterStartupOrResume(PowerOnOffState.ScreenSaver); + + await ExecuteScreenSaverPresetsCustom(args, applicableDevices); return; } @@ -582,7 +622,7 @@ public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, Ca if (!connectedDevices.Any()) { - if (wasConnected) + if (wasConnected == true) { Logger.Debug("Process monitor: TV(s) where connected, but not any longer"); wasConnected = false; @@ -609,7 +649,7 @@ public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, Ca } } - if (!wasConnected) + if (wasConnected == false) { Logger.Debug("Process monitor: TV(s) where not connected, but connection has now been established"); wasConnected = true; @@ -622,6 +662,7 @@ public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, Ca await ExecuteProcessPresets(args, connectedDevices); + await ExecuteScreenSaverPresetsCustom(args, connectedDevices); } catch (Exception ex) { @@ -629,6 +670,18 @@ public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, Ca } } + private async Task ExecuteScreenSaverPresetsCustom(ProcessChangedEventArgs context, IList connectedDevices) + { + var triggerDevices = connectedDevices.Where(d => d.TriggersEnabled).ToList(); + + if (!triggerDevices.Any()) + { + return; + } + + await ExecuteScreenSaverPresets(_serviceManager, context, triggerDevices.Any(d => d.IsUsingHDRPictureMode())); + } + private async Task ExecuteProcessPresets(ProcessChangedEventArgs context, IList connectedDevices) { var triggerDevices = connectedDevices.Where(d => d.TriggersEnabled).ToList(); @@ -648,12 +701,6 @@ private async Task ExecuteProcessPresets(ProcessChangedEventArgs context, IList< return; } - var changedProcesses = new List(); - if (context.ForegroundProcess != null) - { - changedProcesses.Add(context.ForegroundProcess); - } - if (context.ForegroundProcess != null && context.ForegroundProcessIsFullScreen) { presets = presets.Where(p => p.Triggers.Any(t => t.Conditions.HasFlag(PresetConditionType.FullScreen))); @@ -663,19 +710,7 @@ private async Task ExecuteProcessPresets(ProcessChangedEventArgs context, IList< presets = presets.Where(p => p.Triggers.Any(t => !t.Conditions.HasFlag(PresetConditionType.FullScreen))); } - var isHDRActive = triggerDevices.Any(d => d.IsUsingHDRPictureMode()); - - var isGsyncActive = await _serviceManager.HandleExternalServiceAsync("GsyncEnabled", new[] { "" }); - - var triggerContext = new PresetTriggerContext - { - IsHDRActive = isHDRActive, - IsGsyncActive = isGsyncActive, - ForegroundProcess = context.ForegroundProcess, - ForegroundProcessIsFullScreen = context.ForegroundProcessIsFullScreen, - ChangedProcesses = changedProcesses, - IsNotificationDisabled = context.IsNotificationDisabled - }; + var triggerContext = await CreateTriggerContext(_serviceManager, context, triggerDevices.Any(d => d.IsUsingHDRPictureMode())); var toApplyPresets = presets.Where(p => p.Triggers.Any(t => t.TriggerActive(triggerContext))).ToList(); @@ -805,63 +840,53 @@ public void PowerOffDevices(IEnumerable devices, PowerOnOffState state return; } - var error = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED); - try + if (state == PowerOnOffState.ShutDown) { - Logger.Debug($"SetThreadExecutionState: {error}, Thread#: {Environment.CurrentManagedThreadId}"); - if (state == PowerOnOffState.ShutDown) - { - var sleep = Config.ShutdownDelay; - - Logger.Debug($"PowerOffDevices: Waiting for a maximum of {sleep} milliseconds..."); - - while (sleep > 0 && _restartDetector?.PowerOffDetected == false) - { - Thread.Sleep(100); + var sleep = Config.ShutdownDelay; - if (_restartDetector != null && (_restartDetector.RestartDetected || _restartDetector.IsRebootInProgress())) - { - Logger.Debug("Not powering off because of a restart"); - return; - } + Logger.Debug($"PowerOffDevices: Waiting for a maximum of {sleep} milliseconds..."); - sleep -= 100; - } - } - - Logger.Debug("Powering off tv(s)..."); - var tasks = new List(); - foreach (var device in devices) + while (sleep > 0 && _restartDetector?.PowerOffDetected == false) { - var task = device.PowerOff(true, false); - - tasks.Add(task); - } + Thread.Sleep(100); - if (state == PowerOnOffState.StandBy) - { - var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); - if (File.Exists(standByScript)) + if (_restartDetector != null && (_restartDetector.RestartDetected || _restartDetector.IsRebootInProgress())) { - _winApiAdminService.StartProcess(standByScript, hidden: true, wait: true); + Logger.Debug("Not powering off because of a restart"); + return; } + + sleep -= 100; } + } - // We can't use async here because we need to stay on the main thread... - var _ = Task.WhenAll(tasks.ToArray()).ConfigureAwait(true); + Logger.Debug("Powering off tv(s)..."); + var tasks = new List(); + foreach (var device in devices) + { + var task = device.PowerOff(true, false); - Logger.Debug("Done powering off tv(s)"); + tasks.Add(task); } - finally + + if (state == PowerOnOffState.StandBy) { - var error2 = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS); - Logger.Debug($"SetThreadExecutionState (reset): {error2}, Thread#: {Environment.CurrentManagedThreadId}"); + var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); + if (File.Exists(standByScript)) + { + _winApiAdminService.StartProcess(standByScript, hidden: true, wait: true); + } } + + // We can't use async here because we need to stay on the main thread... + var _ = Task.WhenAll(tasks.ToArray()).ConfigureAwait(true); + + Logger.Debug("Done powering off tv(s)"); } - public void PowerOnDevicesTask(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + public Task PowerOnDevicesTask(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) { - Task.Run(async () => await PowerOnDevices(devices, state, checkUserSession)); + return Task.Run(async () => await PowerOnDevices(devices, state, checkUserSession)); } public async Task PowerOnDevices(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) @@ -924,5 +949,10 @@ private void SendConfigToService() PipeUtils.SendMessage(message); } + + public async Task ExecuteEventPresets(PresetTriggerType triggerType) + { + await ExecuteEventPresets(_serviceManager, new[] { triggerType }); + } } } \ No newline at end of file diff --git a/ColorControl/Services/NVIDIA/NvPanel.Designer.cs b/ColorControl/Services/NVIDIA/NvPanel.Designer.cs index 3fbd11d..21e2cc5 100644 --- a/ColorControl/Services/NVIDIA/NvPanel.Designer.cs +++ b/ColorControl/Services/NVIDIA/NvPanel.Designer.cs @@ -80,6 +80,10 @@ private void InitializeComponent() miHDRIncluded = new System.Windows.Forms.ToolStripMenuItem(); miToggleHDR = new System.Windows.Forms.ToolStripMenuItem(); miHDREnabled = new System.Windows.Forms.ToolStripMenuItem(); + miHDROuputMode = new System.Windows.Forms.ToolStripMenuItem(); + miHDROutputModeUnchanged = new System.Windows.Forms.ToolStripMenuItem(); + miHDROutputModeHDR10 = new System.Windows.Forms.ToolStripMenuItem(); + miHDROutputModeHDR10Plus = new System.Windows.Forms.ToolStripMenuItem(); mnuNvDriverSettings = new System.Windows.Forms.ToolStripMenuItem(); miNvDriverSettingsIncluded = new System.Windows.Forms.ToolStripMenuItem(); mnuNvOtherSettings = new System.Windows.Forms.ToolStripMenuItem(); @@ -98,6 +102,8 @@ private void InitializeComponent() miNvTestDithering = new System.Windows.Forms.ToolStripMenuItem(); miNVIDIAInfo = new System.Windows.Forms.ToolStripMenuItem(); lvNvPresetsToolTip = new System.Windows.Forms.ToolTip(components); + mnuNvTriggers = new System.Windows.Forms.ToolStripMenuItem(); + miNvTrigger1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); mnuNvPresets.SuspendLayout(); mnuNvSettings.SuspendLayout(); SuspendLayout(); @@ -328,9 +334,9 @@ private void InitializeComponent() // mnuNvPresets // mnuNvPresets.ImageScalingSize = new System.Drawing.Size(20, 20); - mnuNvPresets.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvApply, miNvPresetApplyOnStartup, tssNvPresetMenu, mnuNvDisplay, mnuNvPresetsColorSettings, mnuNvPresetsColorEnhancements, mnuRefreshRate, mnuNvResolution, miNvPresetDithering, miNvHDR, mnuNvDriverSettings, mnuNvOtherSettings, mnuNvHdmiSettings, mnuNvOverclocking, miNvCopyId }); + mnuNvPresets.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvApply, miNvPresetApplyOnStartup, tssNvPresetMenu, mnuNvDisplay, mnuNvPresetsColorSettings, mnuNvPresetsColorEnhancements, mnuRefreshRate, mnuNvResolution, miNvPresetDithering, miNvHDR, mnuNvDriverSettings, mnuNvOtherSettings, mnuNvHdmiSettings, mnuNvOverclocking, mnuNvTriggers, miNvCopyId }); mnuNvPresets.Name = "mnuNvPresets"; - mnuNvPresets.Size = new System.Drawing.Size(185, 340); + mnuNvPresets.Size = new System.Drawing.Size(185, 362); mnuNvPresets.Opening += mnuNvPresets_Opening; // // miNvApply @@ -377,7 +383,7 @@ private void InitializeComponent() // miNvPresetColorSettings // miNvPresetColorSettings.Name = "miNvPresetColorSettings"; - miNvPresetColorSettings.Size = new System.Drawing.Size(180, 22); + miNvPresetColorSettings.Size = new System.Drawing.Size(120, 22); miNvPresetColorSettings.Text = "Included"; miNvPresetColorSettings.Click += miNvPresetColorSettings_Click; // @@ -391,7 +397,7 @@ private void InitializeComponent() // miNvPresetColorEnhancementsIncluded // miNvPresetColorEnhancementsIncluded.Name = "miNvPresetColorEnhancementsIncluded"; - miNvPresetColorEnhancementsIncluded.Size = new System.Drawing.Size(180, 22); + miNvPresetColorEnhancementsIncluded.Size = new System.Drawing.Size(120, 22); miNvPresetColorEnhancementsIncluded.Text = "Included"; miNvPresetColorEnhancementsIncluded.Click += miNvPresetColorEnhancementsIncluded_Click; // @@ -525,7 +531,7 @@ private void InitializeComponent() // // miNvHDR // - miNvHDR.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miHDRIncluded, miToggleHDR, miHDREnabled }); + miNvHDR.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miHDRIncluded, miToggleHDR, miHDREnabled, miHDROuputMode }); miNvHDR.Name = "miNvHDR"; miNvHDR.Size = new System.Drawing.Size(184, 22); miNvHDR.Text = "HDR"; @@ -533,24 +539,54 @@ private void InitializeComponent() // miHDRIncluded // miHDRIncluded.Name = "miHDRIncluded"; - miHDRIncluded.Size = new System.Drawing.Size(136, 22); + miHDRIncluded.Size = new System.Drawing.Size(142, 22); miHDRIncluded.Text = "Included"; miHDRIncluded.Click += miHDRIncluded_Click; // // miToggleHDR // miToggleHDR.Name = "miToggleHDR"; - miToggleHDR.Size = new System.Drawing.Size(136, 22); + miToggleHDR.Size = new System.Drawing.Size(142, 22); miToggleHDR.Text = "Toggle HDR"; miToggleHDR.Click += miToggleHDR_Click; // // miHDREnabled // miHDREnabled.Name = "miHDREnabled"; - miHDREnabled.Size = new System.Drawing.Size(136, 22); + miHDREnabled.Size = new System.Drawing.Size(142, 22); miHDREnabled.Text = "Enabled"; miHDREnabled.Click += miHDREnabled_Click; // + // miHDROuputMode + // + miHDROuputMode.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miHDROutputModeUnchanged, miHDROutputModeHDR10, miHDROutputModeHDR10Plus }); + miHDROuputMode.Name = "miHDROuputMode"; + miHDROuputMode.Size = new System.Drawing.Size(142, 22); + miHDROuputMode.Text = "Ouput Mode"; + // + // miHDROutputModeUnchanged + // + miHDROutputModeUnchanged.Name = "miHDROutputModeUnchanged"; + miHDROutputModeUnchanged.Size = new System.Drawing.Size(135, 22); + miHDROutputModeUnchanged.Text = "Unchanged"; + miHDROutputModeUnchanged.Click += miHDROutputModeUnchanged_Click; + // + // miHDROutputModeHDR10 + // + miHDROutputModeHDR10.Name = "miHDROutputModeHDR10"; + miHDROutputModeHDR10.Size = new System.Drawing.Size(135, 22); + miHDROutputModeHDR10.Tag = "1"; + miHDROutputModeHDR10.Text = "HDR10"; + miHDROutputModeHDR10.Click += miHDROutputModeUnchanged_Click; + // + // miHDROutputModeHDR10Plus + // + miHDROutputModeHDR10Plus.Name = "miHDROutputModeHDR10Plus"; + miHDROutputModeHDR10Plus.Size = new System.Drawing.Size(135, 22); + miHDROutputModeHDR10Plus.Tag = "2"; + miHDROutputModeHDR10Plus.Text = "HDR10+"; + miHDROutputModeHDR10Plus.Click += miHDROutputModeUnchanged_Click; + // // mnuNvDriverSettings // mnuNvDriverSettings.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvDriverSettingsIncluded }); @@ -691,6 +727,20 @@ private void InitializeComponent() miNVIDIAInfo.Text = "NVIDIA Info"; miNVIDIAInfo.Click += miNVIDIAInfo_Click; // + // mnuNvTriggers + // + mnuNvTriggers.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvTrigger1ToolStripMenuItem }); + mnuNvTriggers.Name = "mnuNvTriggers"; + mnuNvTriggers.Size = new System.Drawing.Size(184, 22); + mnuNvTriggers.Text = "Triggers"; + // + // miNvTrigger1ToolStripMenuItem + // + miNvTrigger1ToolStripMenuItem.Name = "miNvTrigger1ToolStripMenuItem"; + miNvTrigger1ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + miNvTrigger1ToolStripMenuItem.Text = "Trigger 1"; + miNvTrigger1ToolStripMenuItem.Click += miNvTrigger1ToolStripMenuItem_Click; + // // NvPanel // AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -796,5 +846,11 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem miNVIDIAInfo; private System.Windows.Forms.ToolStripMenuItem mnuNvPresetsColorEnhancements; private System.Windows.Forms.ToolStripMenuItem miNvPresetColorEnhancementsIncluded; + private System.Windows.Forms.ToolStripMenuItem miHDROuputMode; + private System.Windows.Forms.ToolStripMenuItem miHDROutputModeUnchanged; + private System.Windows.Forms.ToolStripMenuItem miHDROutputModeHDR10; + private System.Windows.Forms.ToolStripMenuItem miHDROutputModeHDR10Plus; + private System.Windows.Forms.ToolStripMenuItem mnuNvTriggers; + private System.Windows.Forms.ToolStripMenuItem miNvTrigger1ToolStripMenuItem; } } diff --git a/ColorControl/Services/NVIDIA/NvPanel.cs b/ColorControl/Services/NVIDIA/NvPanel.cs index b38472c..670bf7a 100644 --- a/ColorControl/Services/NVIDIA/NvPanel.cs +++ b/ColorControl/Services/NVIDIA/NvPanel.cs @@ -1,4 +1,5 @@ -using ColorControl.Shared.Common; +using ColorControl.Services.Common; +using ColorControl.Shared.Common; using ColorControl.Shared.Contracts; using ColorControl.Shared.Contracts.NVIDIA; using ColorControl.Shared.Forms; @@ -351,6 +352,8 @@ private void mnuNvPresets_Opening(object sender, CancelEventArgs e) miNvOtherIncluded.Visible = presetItemsVisible; mnuNvHdmiSettings.Enabled = preset != null; miNvHdmiIncluded.Visible = presetItemsVisible; + miHDROuputMode.Visible = _nvService.OutputModeAvailable; + mnuNvTriggers.Visible = false; // TODO: implement later if (preset == null) { @@ -369,6 +372,9 @@ private void mnuNvPresets_Opening(object sender, CancelEventArgs e) miNvResolutionIncluded.Visible = !isCurrentDisplay; miRefreshRateIncluded.Visible = !isCurrentDisplay; miHDRIncluded.Visible = !isCurrentDisplay; + miHDROutputModeUnchanged.Visible = !isCurrentDisplay; + miHDROutputModeHDR10.Checked = preset.HdrSettings.OutputMode == NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10; + miHDROutputModeHDR10Plus.Checked = preset.HdrSettings.OutputMode == NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING; if (mnuNvDisplay.DropDownItems.Count == 1) { @@ -1616,7 +1622,7 @@ private void lvNvPresets_MouseMove(object sender, MouseEventArgs e) var text = subItem.Text; - if (index == 7) + if (index == 8) { text = text.Replace(", ", "\r\n"); } @@ -1944,9 +1950,9 @@ private void spatial1ToolStripMenuItem_Click(object sender, EventArgs e) AddOrUpdateItem(); } - public void AfterInitialized() + public async Task AfterInitialized() { - var _ = ApplyNvPresetOnStartup(); + await ApplyNvPresetOnStartup(); } internal void Save() @@ -2004,5 +2010,74 @@ private void miNvPresetColorEnhancementsIncluded_Click(object sender, EventArgs AddOrUpdateItem(); } + + private void miHDROutputModeUnchanged_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + var menuItem = (ToolStripMenuItem)sender; + + var outputMode = menuItem.Tag == null ? default : (string)menuItem.Tag == "1" ? NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10 : NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING; + + if (preset.IsDisplayPreset) + { + if (outputMode == default) + { + return; + } + + _nvService.SetOutputMode(outputMode, preset.Display); + + UpdateDisplayInfoItems(); + return; + } + + preset.HdrSettings.OutputMode = outputMode == default ? null : outputMode; + + AddOrUpdateItem(); + + } + + private void miNvTrigger1ToolStripMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + var menuItem = (ToolStripMenuItem)sender; + + var trigger = preset.Triggers.FirstOrDefault() ?? new PresetTrigger(); + var triggerTypes = Utils.EnumToDictionary(new[] { PresetTriggerType.ProcessSwitch, PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop, PresetTriggerType.Reserved5 }).Select(d => d.Value); + var conditions = Utils.GetDescriptions(fromValue: 1); + + var eventField = new FieldDefinition + { + Label = "Event", + FieldType = FieldType.DropDown, + Values = triggerTypes, + Value = trigger.Trigger.GetDescription() + }; + + var conditionsField = new FieldDefinition + { + Label = "Conditions", + FieldType = FieldType.Flags, + Values = conditions, + Value = trigger.Conditions + }; + + var values = MessageForms.ShowDialog("Edit trigger 1", new[] { + eventField, + conditionsField + }); + if (!values.Any()) + { + return; + } + + trigger.Trigger = eventField.ValueAsEnum(); + trigger.Conditions = conditionsField.ValueAsEnum(); + + preset.UpdateTrigger(trigger.Trigger, trigger.Conditions); + + AddOrUpdateItem(); + + } } } diff --git a/ColorControl/Services/NVIDIA/NvPreset.cs b/ColorControl/Services/NVIDIA/NvPreset.cs index 4bb50c7..e34827b 100644 --- a/ColorControl/Services/NVIDIA/NvPreset.cs +++ b/ColorControl/Services/NVIDIA/NvPreset.cs @@ -47,6 +47,7 @@ class NvPreset : PresetBase public bool applyHdmiSettings { get; set; } public NvHdmiInfoFrameSettings HdmiInfoFrameSettings { get; set; } public NvColorProfileSettings ColorProfileSettings { get; set; } + public NvHdrSettings HdrSettings { get; set; } [JsonIgnore] public bool IsDisplayPreset { get; set; } @@ -80,6 +81,7 @@ public NvPreset() : base() HdmiInfoFrameSettings = new NvHdmiInfoFrameSettings(); ColorProfileSettings = new NvColorProfileSettings(); ColorEnhancementSettings = new NvColorEnhancementSettings(); + HdrSettings = new NvHdrSettings(); } public NvPreset(ColorData colorData) : this() @@ -102,6 +104,8 @@ public NvPreset(NvPreset preset) : this() applyHDR = preset.applyHDR; HDREnabled = preset.HDREnabled; toggleHDR = preset.toggleHDR; + HdrSettings = new NvHdrSettings(preset.HdrSettings); + applyDithering = preset.applyDithering; ditheringEnabled = preset.ditheringEnabled; applyRefreshRate = preset.applyRefreshRate; @@ -168,7 +172,9 @@ public override List GetDisplayValues(Config config = null) var dithering = GetDitheringDescription(); values.Add(FormatDisplaySetting(dithering, isCurrent, applyDithering)); - values.Add(FormatDisplaySetting(toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled", isCurrent, applyHDR)); + + var hdrEnabledDesc = HDREnabled ? $"Enabled{(HdrSettings.OutputMode.HasValue ? (HdrSettings.OutputMode == NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING ? " (HDR10+)" : " (HDR10)") : "")}" : ""; + values.Add(FormatDisplaySetting(toggleHDR ? "Toggle" : HDREnabled ? hdrEnabledDesc : "Disabled", isCurrent, applyHDR)); values.Add(FormatDisplaySetting(GetDriverSettingsDescription(), isCurrent, applyDriverSettings)); diff --git a/ColorControl/Services/NVIDIA/NvService.cs b/ColorControl/Services/NVIDIA/NvService.cs index ca17db5..9fc7759 100644 --- a/ColorControl/Services/NVIDIA/NvService.cs +++ b/ColorControl/Services/NVIDIA/NvService.cs @@ -1,4 +1,5 @@ using ColorControl.Services.Common; +using ColorControl.Services.EventDispatcher; using ColorControl.Shared.Common; using ColorControl.Shared.Contracts; using ColorControl.Shared.Contracts.NVIDIA; @@ -13,10 +14,12 @@ using NvAPIWrapper.Display; using NvAPIWrapper.GPU; using NvAPIWrapper.Native; +using NvAPIWrapper.Native.Attributes; using NvAPIWrapper.Native.Display; using NvAPIWrapper.Native.Display.Structures; using NvAPIWrapper.Native.General.Structures; using NvAPIWrapper.Native.GPU.Structures; +using NvAPIWrapper.Native.Helpers; using NvAPIWrapper.Native.Interfaces.Display; using NWin32; using NWin32.NativeTypes; @@ -28,6 +31,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using static ColorControl.Shared.Contracts.NVIDIA.NvHdrSettings; using static ColorControl.Shared.Native.CCD; namespace ColorControl.Services.NVIDIA @@ -74,6 +78,7 @@ class NvService : GraphicsService PreserveSig = true)] private static extern IntPtr NvAPI64_QueryInterface(uint interfaceId); + [FunctionId(FunctionId.NvAPI_Disp_SetDitherControl)] public delegate int NvAPI_Disp_SetDitherControl( [In] PhysicalGPUHandle physicalGpu, [In] uint OutputId, @@ -82,6 +87,7 @@ public delegate int NvAPI_Disp_SetDitherControl( [In] uint mode ); + [FunctionId(FunctionId.NvAPI_Disp_GetDitherControl)] public delegate int NvAPI_Disp_GetDitherControl( [In] uint DisplayId, [MarshalAs(UnmanagedType.Struct)] ref NV_GPU_DITHER_CONTROL_V1 ditherControl); @@ -97,6 +103,47 @@ public struct NV_GPU_DITHER_CONTROL_V1 public uint modeCaps; }; + [FunctionId(FunctionId.NvAPI_Disp_SetOutputMode)] + public delegate int NvAPI_Disp_SetOutputMode( + [In] uint DisplayId, + ref NV_DISPLAY_OUTPUT_MODE pDisplayMode + ); + + [FunctionId(FunctionId.NvAPI_Disp_GetOutputMode)] + public delegate int NvAPI_Disp_GetOutputMode( + [In] uint DisplayId, + ref NV_DISPLAY_OUTPUT_MODE pDisplayMode + ); + + //public struct NV_HDR_METADATA_V1 + //{ + // public uint version; //!< Version of this structure + + // public short displayPrimary_x0; //!< x coordinate of color primary 0 (e.g. Red) of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + // public short displayPrimary_y0; //!< y coordinate of color primary 0 (e.g. Red) of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + + // public short displayPrimary_x1; //!< x coordinate of color primary 1 (e.g. Green) of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + // public short displayPrimary_y1; //!< y coordinate of color primary 1 (e.g. Green) of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + + // public short displayPrimary_x2; //!< x coordinate of color primary 2 (e.g. Blue) of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + // public short displayPrimary_y2; //!< y coordinate of color primary 2 (e.g. Blue) of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + + // public short displayWhitePoint_x; //!< x coordinate of white point of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + // public short displayWhitePoint_y; //!< y coordinate of white point of mastering display ([0x0000-0xC350] = [0.0 - 1.0]) + + // public short max_display_mastering_luminance; //!< Maximum display mastering luminance ([0x0000-0xFFFF] = [0.0 - 65535.0] cd/m^2, in units of 1 cd/m^2) + // public short min_display_mastering_luminance; //!< Minimum display mastering luminance ([0x0000-0xFFFF] = [0.0 - 6.55350] cd/m^2, in units of 0.0001 cd/m^2) + + // public short max_content_light_level; //!< Maximum Content Light level (MaxCLL) ([0x0000-0xFFFF] = [0.0 - 65535.0] cd/m^2, in units of 1 cd/m^2) + // public short max_frame_average_light_level; //!< Maximum Frame-Average Light Level (MaxFALL) ([0x0000-0xFFFF] = [0.0 - 65535.0] cd/m^2, in units of 1 cd/m^2) + //} + + //public delegate int NvAPI_Disp_GetSourceHdrMetadata( + // [In] uint DisplayId, + // [MarshalAs(UnmanagedType.Struct)] ref NV_HDR_METADATA_V1 pMetadata, + // [In] long sourcePID + //); + public override string ServiceName => "NVIDIA"; protected override string PresetsBaseFilename => "NvPresets.json"; @@ -105,6 +152,7 @@ public struct NV_GPU_DITHER_CONTROL_V1 private string _baseProfileName = ""; private string _configFilename; public NvServiceConfig Config { get; private set; } + private NV_DISPLAY_OUTPUT_MODE? Hdr10OutputModeForced { get; set; } private DrsSettingsService _drs; private DrsSettingsMetaService _meta; @@ -131,6 +179,9 @@ public struct NV_GPU_DITHER_CONTROL_V1 public const uint DRS_RBAR_OPTIONS = 0X000F00BB; public const uint DRS_RBAR_SIZE_LIMIT = 0X000F00FF; + public uint DriverVersion { get; private set; } + public bool OutputModeAvailable => DriverVersion >= 52500; + private static readonly List _driverSettingIds = new() { DRS_GSYNC_APPLICATION_MODE, @@ -148,12 +199,16 @@ public struct NV_GPU_DITHER_CONTROL_V1 }; private readonly WinApiService _winApiService; private readonly RpcClientService _rpcClientService; + private readonly PowerEventDispatcher _powerEventDispatcher; + private readonly ServiceManager _serviceManager; private static NvService ServiceInstance; - public NvService(AppContextProvider appContextProvider, WinApiService winApiService, RpcClientService rpcClientService) : base(appContextProvider) + public NvService(AppContextProvider appContextProvider, WinApiService winApiService, RpcClientService rpcClientService, PowerEventDispatcher powerEventDispatcher, ServiceManager serviceManager) : base(appContextProvider) { _winApiService = winApiService; _rpcClientService = rpcClientService; + _powerEventDispatcher = powerEventDispatcher; + _serviceManager = serviceManager; _rpcClientService.Name = nameof(NvService); NvPreset.NvService = this; @@ -162,6 +217,14 @@ public NvService(AppContextProvider appContextProvider, WinApiService winApiServ LoadPresets(); } + public void InstallEventHandlers() + { + // TODO: implement later + //_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Suspend, PowerModeChanged); + //_powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Resume, PowerModeResume); + //_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Shutdown, PowerModeChanged); + } + public static async Task ExecutePresetAsync(string idOrName) { try @@ -354,14 +417,22 @@ public override async Task ApplyPreset(NvPreset preset) SetColorData(display, preset.colorData); } - if (applyHdr) + if (preset.applyHDR) { - if (newHdrEnabled && !hdrEnabled) + if (applyHdr) { - MainWindow.BeforeDisplaySettingsChange(); + if (newHdrEnabled && !hdrEnabled) + { + MainWindow.BeforeDisplaySettingsChange(); + } + + SetHDRState(display, newHdrEnabled); } - SetHDRState(display, newHdrEnabled); + if (newHdrEnabled && preset.HdrSettings.OutputMode.HasValue) + { + SetOutputMode(preset.HdrSettings.OutputMode.Value, display); + } } if (preset.applyRefreshRate || preset.applyResolution) @@ -381,7 +452,7 @@ public override async Task ApplyPreset(NvPreset preset) if (preset.ColorProfileSettings.ProfileName != null) { - CCD.SetDisplayDefaultColorProfile(display.Name, preset.ColorProfileSettings.ProfileName); + CCD.SetDisplayDefaultColorProfile(display.Name, preset.ColorProfileSettings.ProfileName, _appContextProvider.GetAppContext().Config.SetMinTmlAndMaxTml); } } @@ -575,6 +646,12 @@ private bool ColorDataDiffers(ColorData colorData) public void SetHDRState(Display display, bool enabled) { + if (!enabled && Hdr10OutputModeForced.HasValue) + { + SetOutputMode(NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_SDR, display); + Hdr10OutputModeForced = null; + } + CCD.SetHDRState(enabled, display.Name); } @@ -637,48 +714,128 @@ public int GetHueAngle(Display display) return display.HUEControl.CurrentAngle; } + //public NV_HDR_METADATA_V1? GetHdrMetaData(Display display = null) + //{ + // var ptr = NvAPI64_QueryInterface(0x0D3F52DA); + // if (ptr != IntPtr.Zero) + // { + // var delegateValue = Marshal.GetDelegateForFunctionPointer(ptr, typeof(NvAPI_Disp_GetSourceHdrMetadata)) as NvAPI_Disp_GetSourceHdrMetadata; + + // display ??= GetCurrentDisplay(); + + // var displayDevice = display.DisplayDevice; + + // var displayId = displayDevice.DisplayId; + + // var metadata = new NV_HDR_METADATA_V1(); + // metadata.version = MAKE_NVAPI_VERSION(1); + + // var processId = Process.GetCurrentProcess().Id; + + // var resultValue = delegateValue(displayId, ref metadata, processId); + + // return resultValue == 0 ? metadata : null; + // } + + // return null; + //} + + public bool SetOutputMode(NV_DISPLAY_OUTPUT_MODE outputMode, Display display = null) + { + var delegateValue = DelegateFactory.GetDelegate(); + + if (delegateValue == null) + { + return false; + } + + display ??= GetCurrentDisplay(); + + var displayDevice = display.DisplayDevice; + + var displayId = displayDevice.DisplayId; + + var newOutputMode = outputMode; + var previousOutputMode = Hdr10OutputModeForced; + + var resultValue = delegateValue(displayId, ref outputMode); + + if (resultValue == 0) + { + Hdr10OutputModeForced = newOutputMode > NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_SDR ? newOutputMode : null; + } + + if (previousOutputMode == NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING && newOutputMode == NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10) + { + // HDR10+ will not be disabled properly when setting output mode to HDR10, just disable and enable HDR again + SetHDRState(display, false); + SetHDRState(display, true); + } + + return resultValue == 0; + } + + public NV_DISPLAY_OUTPUT_MODE? GetOutputMode(Display display = null) + { + var delegateValue = DelegateFactory.GetDelegate(); + + if (delegateValue == null) + { + return null; + } + + + display ??= GetCurrentDisplay(); + + var displayDevice = display.DisplayDevice; + + var displayId = displayDevice.DisplayId; + + var outputMode = NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_SDR; + + var resultValue = delegateValue(displayId, ref outputMode); + + return resultValue == 0 ? outputMode : null; + } + public bool SetDithering(NvDitherState state, uint bits = 1, uint mode = 4, NvPreset preset = null, Display currentDisplay = null) { - var result = true; + var delegateValue = DelegateFactory.GetDelegate(); - var ptr = NvAPI64_QueryInterface(0xDF0DFCDD); - if (ptr != IntPtr.Zero) + if (delegateValue == null) { - var delegateValue = Marshal.GetDelegateForFunctionPointer(ptr, typeof(NvAPI_Disp_SetDitherControl)) as NvAPI_Disp_SetDitherControl; + return false; + } - var display = currentDisplay ?? GetCurrentDisplay(); - if (display == null) - { - return false; - } + var result = true; - var displayDevice = display.DisplayDevice; + var display = currentDisplay ?? GetCurrentDisplay(); + if (display == null) + { + return false; + } - var gpuHandle = displayDevice.PhysicalGPU.Handle; - var displayId = displayDevice.DisplayId; + var displayDevice = display.DisplayDevice; - if (state == NvDitherState.Auto) - { - // These seem to be ignored in Auto-state - bits = 0; - mode = 0; - } - else if (preset != null) - { - bits = preset.ditheringBits; - mode = preset.ditheringMode; - } + var gpuHandle = displayDevice.PhysicalGPU.Handle; + var displayId = displayDevice.DisplayId; - var resultValue = delegateValue(gpuHandle, displayId, (uint)state, bits, mode); - if (resultValue != 0) - { - Logger.Error($"Could not set dithering because NvAPI_Disp_SetDitherControl returned a non-zero return code: {resultValue}"); - result = false; - } + if (state == NvDitherState.Auto) + { + // These seem to be ignored in Auto-state + bits = 0; + mode = 0; } - else + else if (preset != null) + { + bits = preset.ditheringBits; + mode = preset.ditheringMode; + } + + var resultValue = delegateValue(gpuHandle, displayId, (uint)state, bits, mode); + if (resultValue != 0) { - Logger.Error($"Could not set dithering because the function NvAPI_Disp_SetDitherControl could not be found"); + Logger.Error($"Could not set dithering because NvAPI_Disp_SetDitherControl returned a non-zero return code: {resultValue}"); result = false; } @@ -687,33 +844,32 @@ public bool SetDithering(NvDitherState state, uint bits = 1, uint mode = 4, NvPr public NV_GPU_DITHER_CONTROL_V1 GetDithering(Display currentDisplay = null) { - var dither = new NV_GPU_DITHER_CONTROL_V1 { version = 0x10018 }; + var version = MAKE_NVAPI_VERSION(1); + var dither = new NV_GPU_DITHER_CONTROL_V1 { version = version }; - var ptr = NvAPI64_QueryInterface(0x932AC8FB); - if (ptr != IntPtr.Zero) + var delegateValue = DelegateFactory.GetDelegate(); + + if (delegateValue == null) { - var delegateValue = Marshal.GetDelegateForFunctionPointer(ptr, typeof(NvAPI_Disp_GetDitherControl)) as NvAPI_Disp_GetDitherControl; + dither.state = -2; + return dither; + } - var display = currentDisplay ?? GetCurrentDisplay(); - if (display == null) - { - dither.state = -1; - return dither; - } + var display = currentDisplay ?? GetCurrentDisplay(); + if (display == null) + { + dither.state = -1; + return dither; + } - var displayDevice = display.DisplayDevice; - var displayId = displayDevice.DisplayId; + var displayDevice = display.DisplayDevice; + var displayId = displayDevice.DisplayId; - var result = delegateValue(displayId, ref dither); - if (result != 0) - { - Logger.Error($"Could not get dithering because NvAPI_Disp_GetDitherControl returned a non-zero return code: {result}"); - dither.state = -1; - } - } - else + var result = delegateValue(displayId, ref dither); + if (result != 0) { - dither.state = -2; + Logger.Error($"Could not get dithering because NvAPI_Disp_GetDitherControl returned a non-zero return code: {result}"); + dither.state = -1; } return dither; @@ -879,18 +1035,14 @@ public void SetHDMIContentType(Display display, InfoFrameVideoContentType conten public InfoFrameVideoContentType GetHDMIContentType(Display display) { - var displayDevice = display.DisplayDevice; - - var info = displayDevice.HDMIVideoFrameOverrideInformation ?? displayDevice.HDMIVideoFrameCurrentInformation; + var info = GetInfoFrameVideo(display); return info.HasValue ? info.Value.ContentType : InfoFrameVideoContentType.Auto; } public NvHdmiInfoFrameSettings GetHdmiSettings(Display display) { - var displayDevice = display.DisplayDevice; - - var info = displayDevice.HDMIVideoFrameOverrideInformation ?? displayDevice.HDMIVideoFrameCurrentInformation; + var info = GetInfoFrameVideo(display); if (!info.HasValue) { @@ -911,6 +1063,13 @@ public NvHdmiInfoFrameSettings GetHdmiSettings(Display display) }; } + private static InfoFrameVideo? GetInfoFrameVideo(Display display) + { + var displayDevice = display.DisplayDevice; + + return Logger.Swallow(() => displayDevice.HDMIVideoFrameOverrideInformation) ?? Logger.Swallow(() => displayDevice.HDMIVideoFrameCurrentInformation); + } + public Scaling GetScaling(Display display) { var pathTargetInfo = GetTargetInfoForDisplay(display); @@ -972,11 +1131,15 @@ public void SetScaling(Display display, Scaling scaling) public void SetColorProfile(Display display, string name) { - CCD.SetDisplayDefaultColorProfile(display.Name, name); + CCD.SetDisplayDefaultColorProfile(display.Name, name, _appContextProvider.GetAppContext().Config.SetMinTmlAndMaxTml); } public void TestResolution() { + //GetHdrMetaData(); + + //SetOutputMode(NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING); + //var display = GetCurrentDisplay(); //CCD.GetUsePerUserDisplayProfiles(display.Name); @@ -1157,6 +1320,7 @@ public NvPreset GetPresetForDisplay(Display display, bool driverSettings = false var preset = new NvPreset { Display = display, IsDisplayPreset = true, applyColorData = false, primaryDisplay = isPrimaryDisplay }; preset.HDREnabled = IsHDREnabled(display); + preset.HdrSettings.OutputMode = preset.HDREnabled && OutputModeAvailable ? GetOutputMode(display) : null; preset.displayName = FormUtils.ExtendedDisplayName(display.Name); preset.colorData = GetCurrentColorData(display); @@ -1284,6 +1448,9 @@ protected override void Initialize() NvAPIWrapper.NVIDIA.Initialize(); _initialized = true; + var version = default(string); + DriverVersion = GeneralApi.GetDriverAndBranchVersion(out version); + _drs = DrsServiceLocator.SettingService; _meta = DrsServiceLocator.MetaService; @@ -1415,5 +1582,53 @@ internal Display ResolveDisplay(NvPreset preset) return preset.Display; } + + private async Task PowerModeResume(object sender, PowerStateChangedEventArgs e, CancellationToken _) + { + Logger.Debug($"PowerModeChanged: {e.State}"); + + // Wait + await Task.Delay(30000); + + ExecutePresetsForEvent(PresetTriggerType.Resume); + } + + private void PowerModeChanged(object sender, PowerStateChangedEventArgs e) + { + Logger.Debug($"PowerModeChanged: {e.State}"); + + switch (e.State) + { + case PowerOnOffState.StandBy: + { + ExecutePresetsForEvent(PresetTriggerType.Standby); + break; + } + case PowerOnOffState.ShutDown: + { + ExecutePresetsForEvent(PresetTriggerType.Shutdown); + break; + } + } + } + + private void ExecutePresetsForEvent(PresetTriggerType triggerType) + { + Logger.Debug($"Executing presets for event {triggerType}"); + + var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == triggerType)).ToList(); + + if (!presets.Any()) + { + return; + } + + ExecuteEventPresets(_serviceManager, new[] { triggerType }).Wait(); + } + + private static uint MAKE_NVAPI_VERSION(int version) + { + return (uint)((Marshal.SizeOf(typeof(T))) | version << 16); + } } } diff --git a/ColorControl/Services/Samsung/SamsungPanel.cs b/ColorControl/Services/Samsung/SamsungPanel.cs index b33ccb5..cdf90f2 100644 --- a/ColorControl/Services/Samsung/SamsungPanel.cs +++ b/ColorControl/Services/Samsung/SamsungPanel.cs @@ -1,5 +1,6 @@ using ColorControl.Services.AMD; using ColorControl.Services.Common; +using ColorControl.Services.EventDispatcher; using ColorControl.Services.NVIDIA; using ColorControl.Shared.Common; using ColorControl.Shared.Contracts; @@ -11,6 +12,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -27,17 +29,17 @@ public partial class SamsungPanel : UserControl, IModulePanel private NvService _nvService; private AmdService _amdService; private IntPtr _mainHandle; - + private readonly PowerEventDispatcher _powerEventDispatcher; private string _tabMessage; private bool _disableEvents = false; - internal SamsungPanel(SamsungService samsungService, NvService nvService, AmdService amdService, IntPtr handle) + internal SamsungPanel(SamsungService samsungService, NvService nvService, AmdService amdService, IntPtr handle, PowerEventDispatcher powerEventDispatcher) { _samsungService = samsungService; _nvService = nvService; _amdService = amdService; _mainHandle = handle; - + _powerEventDispatcher = powerEventDispatcher; _config = Shared.Common.GlobalContext.CurrentContext.Config; InitializeComponent(); @@ -70,10 +72,16 @@ internal SamsungPanel(SamsungService samsungService, NvService nvService, AmdSer public void Init() { var _ = Handle; - _samsungService.RefreshDevices(afterStartUp: true).ContinueWith((_) => FormUtils.BeginInvokeCheck(this, AfterSamsungServiceRefreshDevices)); + _powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Startup, PowerStateChanged); + _samsungService.InstallEventHandlers(); } + private async Task PowerStateChanged(object sender, PowerStateChangedEventArgs e, CancellationToken token) + { + await _samsungService.RefreshDevices(afterStartUp: true).ContinueWith((_) => FormUtils.BeginInvokeCheck(this, AfterSamsungServiceRefreshDevices)); + } + private void _samsungService_SelectedDeviceChangedEvent(object sender, EventArgs e) { FormUtils.BeginInvokeCheck(this, () => SetSamsungDevicesSelectedIndex(sender)); @@ -90,7 +98,7 @@ private void SetSamsungDevicesSelectedIndex(object sender) cbxSamsungDevices.SelectedIndex = cbxSamsungDevices.Items.IndexOf(sender); } - private void AfterSamsungServiceRefreshDevices() + private async Task AfterSamsungServiceRefreshDevices() { FillSamsungDevices(); @@ -98,7 +106,13 @@ private void AfterSamsungServiceRefreshDevices() if (startUpParams.ExecuteSamsungPreset) { - var _ = _samsungService.ApplyPreset(startUpParams.SamsungPresetName); + await _samsungService.ApplyPreset(startUpParams.SamsungPresetName); + return; + } + + if (startUpParams.RunningFromScheduledTask) + { + await _samsungService.ExecuteEventPresets(PresetTriggerType.Startup); } } @@ -318,7 +332,7 @@ public void UpdateInfo() //chkSamsungRemoteControlShow.Checked = _samsungService.Config.ShowRemoteControl; //scSamsungController.Panel2Collapsed = !_samsungService.Config.ShowRemoteControl; - FormUtils.BuildComboBox(cbxSamsungPresetTrigger, PresetTriggerType.Resume, PresetTriggerType.Shutdown, PresetTriggerType.Standby, PresetTriggerType.Startup, PresetTriggerType.Reserved5, PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop); + FormUtils.BuildComboBox(cbxSamsungPresetTrigger, PresetTriggerType.Reserved5); if (!string.IsNullOrEmpty(_tabMessage)) { @@ -649,7 +663,7 @@ private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e) return; } - if (e.Index is 0 or 1 or 4 or 7 && !(device.Options.PowerOnAfterResume || device.Options.PowerOnAfterStartup || device.Options.PowerOnAfterScreenSaver || device.Options.PowerOnByWindows)) + if (e.Index is 0 or 1 or 5 or 8 && !(device.Options.PowerOnAfterResume || device.Options.PowerOnAfterStartup || device.Options.PowerOnAfterScreenSaver || device.Options.PowerOnByWindows)) { MessageForms.InfoOk( @"Be sure to activate the following setting on the TV, or the app will not be able to wake the TV: @@ -863,7 +877,7 @@ private void btnSamsungPresetEditTriggerConditions_Click(object sender, EventArg } var value = (PresetConditionType)values.First().Value; - edtSamsungPresetTriggerConditions.Tag = (PresetConditionType)values.First().Value; + edtSamsungPresetTriggerConditions.Tag = value; edtSamsungPresetTriggerConditions.Text = Utils.GetDescriptions((int)value).Join(", "); } diff --git a/ColorControl/Services/Samsung/SamsungService.cs b/ColorControl/Services/Samsung/SamsungService.cs index 8973b7f..a048360 100644 --- a/ColorControl/Services/Samsung/SamsungService.cs +++ b/ColorControl/Services/Samsung/SamsungService.cs @@ -37,7 +37,6 @@ class SamsungService : ServiceBase, ISamsungService private string _configFilename; private bool _poweredOffByScreenSaver; private int _poweredOffByScreenSaverProcessId; - private object _lastTriggeredPreset; private List _samsungApps = new List(); private Shared.Common.GlobalContext _appContext; @@ -88,7 +87,7 @@ public void InstallEventHandlers() _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOn, PowerModeChanged); _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOff, PowerModeChanged); _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Suspend, PowerModeChanged); - _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Resume, PowerModeChanged); + _powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Resume, PowerModeResume); _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Shutdown, PowerModeChanged); _processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged); } @@ -112,25 +111,39 @@ private void LoadConfig() SamsungPreset.SamsungDevices = Config.Devices; } + private async Task PowerModeResume(object sender, PowerStateChangedEventArgs e, CancellationToken _) + { + Logger.Debug($"PowerModeChanged: {e.State}"); + + await WakeAfterStartupOrResume(PowerOnOffState.Resume); + + ExecutePresetsForEvent(PresetTriggerType.Resume); + } + private void PowerModeChanged(object sender, PowerStateChangedEventArgs e) { Logger.Debug($"PowerModeChanged: {e.State}"); + if (Devices?.Any() != true) + { + Logger.Debug("Devices have not been loaded, ignoring event"); + return; + } + switch (e.State) { - case PowerOnOffState.Resume: - { - WakeAfterStartupOrResume(PowerOnOffState.Resume); - break; - } case PowerOnOffState.StandBy: { + ExecutePresetsForEvent(PresetTriggerType.Standby); + var devices = Devices.Where(d => d.Options.PowerOffOnStandby); PowerOffDevices(devices, PowerOnOffState.StandBy); break; } case PowerOnOffState.ShutDown: { + ExecutePresetsForEvent(PresetTriggerType.Shutdown); + var devices = Devices.Where(d => d.Options.PowerOffOnShutdown); PowerOffDevices(devices); @@ -152,6 +165,28 @@ private void PowerModeChanged(object sender, PowerStateChangedEventArgs e) } } + private void ExecutePresetsForEvent(PresetTriggerType triggerType) + { + Logger.Debug($"Executing presets for event {triggerType}"); + + var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == triggerType)).ToList(); + + if (!presets.Any()) + { + return; + } + + var applicableDevices = Devices.Where(d => d.Options.TriggersEnabled && + presets.Any(p => (string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase))).ToList(); + + if (!applicableDevices.Any()) + { + return; + } + + ExecuteEventPresets(_serviceManager, new[] { triggerType }).Wait(); + } + public async Task RefreshDevices(bool connect = true, bool afterStartUp = false) { Devices = Config.Devices; @@ -201,7 +236,7 @@ public async Task RefreshDevices(bool connect = true, bool afterStartUp = false) { if (_allowPowerOn) { - WakeAfterStartupOrResume(); + await WakeAfterStartupOrResume(); } else { @@ -308,7 +343,7 @@ internal void RemoveCustomDevice(SamsungDevice device) } } - private void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + private Task WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) { Devices.ForEach(d => d.ClearPowerOffTask()); @@ -316,7 +351,7 @@ private void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.St state == PowerOnOffState.Resume && d.Options.PowerOnAfterResume || state == PowerOnOffState.ScreenSaver && d.Options.PowerOnAfterScreenSaver); - PowerOnDevicesTask(wakeDevices, state, checkUserSession); + return PowerOnDevicesTask(wakeDevices, state, checkUserSession); } private void PowerOffDevices(IEnumerable devices, PowerOnOffState state = PowerOnOffState.ShutDown) @@ -332,63 +367,53 @@ private void PowerOffDevices(IEnumerable devices, PowerOnOffState return; } - var error = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED); - try + if (state == PowerOnOffState.ShutDown) { - Logger.Debug($"SetThreadExecutionState: {error}, Thread#: {Environment.CurrentManagedThreadId}"); - if (state == PowerOnOffState.ShutDown) - { - var sleep = Config.ShutdownDelay; - - Logger.Debug($"PowerOffDevices: Waiting for a maximum of {sleep} milliseconds..."); - - while (sleep > 0 && _restartDetector?.PowerOffDetected == false) - { - Thread.Sleep(100); - - if (_restartDetector != null && (_restartDetector.RestartDetected || _restartDetector.IsRebootInProgress())) - { - Logger.Debug("Not powering off because of a restart"); - return; - } + var sleep = Config.ShutdownDelay; - sleep -= 100; - } - } + Logger.Debug($"PowerOffDevices: Waiting for a maximum of {sleep} milliseconds..."); - Logger.Debug("Powering off tv(s)..."); - var tasks = new List(); - foreach (var device in devices) + while (sleep > 0 && _restartDetector?.PowerOffDetected == false) { - var task = device.PowerOffAsync(); - - tasks.Add(task); - } + Thread.Sleep(100); - if (state == PowerOnOffState.StandBy) - { - var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); - if (File.Exists(standByScript)) + if (_restartDetector != null && (_restartDetector.RestartDetected || _restartDetector.IsRebootInProgress())) { - _winApiAdminService.StartProcess(standByScript, hidden: true, wait: true); + Logger.Debug("Not powering off because of a restart"); + return; } + + sleep -= 100; } + } - // We can't use async here because we need to stay on the main thread... - var _ = Task.WhenAll(tasks.ToArray()).ConfigureAwait(true); + Logger.Debug("Powering off tv(s)..."); + var tasks = new List(); + foreach (var device in devices) + { + var task = device.PowerOffAsync(); - Logger.Debug("Done powering off tv(s)"); + tasks.Add(task); } - finally + + if (state == PowerOnOffState.StandBy) { - var error2 = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS); - Logger.Debug($"SetThreadExecutionState (reset): {error2}, Thread#: {Environment.CurrentManagedThreadId}"); + var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); + if (File.Exists(standByScript)) + { + _winApiAdminService.StartProcess(standByScript, hidden: true, wait: true); + } } + + // We can't use async here because we need to stay on the main thread... + var _ = Task.WhenAll(tasks.ToArray()).ConfigureAwait(true); + + Logger.Debug("Done powering off tv(s)"); } - private void PowerOnDevicesTask(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + private Task PowerOnDevicesTask(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) { - Task.Run(async () => await PowerOnDevices(devices, state, checkUserSession)); + return Task.Run(async () => await PowerOnDevices(devices, state, checkUserSession)); } private async Task PowerOnDevices(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) @@ -460,10 +485,15 @@ private async Task ProcessChanged(object sender, ProcessChangedEventArgs args, C { try { - var wasConnected = Devices.Any(d => d.IsConnected()); + var wasConnected = Devices?.Any(d => d.IsConnected()); + + if (!wasConnected.HasValue) + { + return; + } var applicableDevices = Devices.Where(d => d.Options.PowerOffOnScreenSaver || d.Options.PowerOnAfterScreenSaver || d.Options.TriggersEnabled && _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None) && - ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))); + ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))).ToList(); if (!applicableDevices.Any()) { @@ -498,7 +528,9 @@ private async Task ProcessChanged(object sender, ProcessChangedEventArgs args, C _poweredOffByScreenSaver = false; _poweredOffByScreenSaverProcessId = 0; - WakeAfterStartupOrResume(PowerOnOffState.ScreenSaver); + await WakeAfterStartupOrResume(PowerOnOffState.ScreenSaver); + + await ExecuteScreenSaverPresetsCustom(args, applicableDevices); return; } @@ -507,7 +539,7 @@ private async Task ProcessChanged(object sender, ProcessChangedEventArgs args, C if (!connectedDevices.Any()) { - if (wasConnected) + if (wasConnected == true) { Logger.Debug("Process monitor: TV(s) where connected, but not any longer"); wasConnected = false; @@ -534,7 +566,7 @@ private async Task ProcessChanged(object sender, ProcessChangedEventArgs args, C } } - if (!wasConnected) + if (wasConnected == false) { Logger.Debug("Process monitor: TV(s) where not connected, but connection has now been established"); wasConnected = true; @@ -547,6 +579,7 @@ private async Task ProcessChanged(object sender, ProcessChangedEventArgs args, C await ExecuteProcessPresets(args, connectedDevices); + await ExecuteScreenSaverPresetsCustom(args, connectedDevices); } catch (Exception ex) { @@ -554,6 +587,18 @@ private async Task ProcessChanged(object sender, ProcessChangedEventArgs args, C } } + private async Task ExecuteScreenSaverPresetsCustom(ProcessChangedEventArgs context, IList connectedDevices) + { + var triggerDevices = connectedDevices.Where(d => d.Options.TriggersEnabled).ToList(); + + if (!triggerDevices.Any()) + { + return; + } + + await ExecuteScreenSaverPresets(_serviceManager, context); + } + private async Task ExecuteProcessPresets(ProcessChangedEventArgs context, IList connectedDevices) { var triggerDevices = connectedDevices.Where(d => d.Options.TriggersEnabled).ToList(); @@ -588,19 +633,7 @@ private async Task ExecuteProcessPresets(ProcessChangedEventArgs context, IList< presets = presets.Where(p => p.Triggers.Any(t => !t.Conditions.HasFlag(PresetConditionType.FullScreen))); } - var isHDRActive = CCD.IsHDREnabled(); - - var isGsyncActive = await _serviceManager.HandleExternalServiceAsync("GsyncEnabled", new[] { "" }); - - var triggerContext = new PresetTriggerContext - { - IsHDRActive = isHDRActive, - IsGsyncActive = isGsyncActive, - ForegroundProcess = context.ForegroundProcess, - ForegroundProcessIsFullScreen = context.ForegroundProcessIsFullScreen, - ChangedProcesses = changedProcesses, - IsNotificationDisabled = context.IsNotificationDisabled - }; + var triggerContext = await CreateTriggerContext(_serviceManager, context); var toApplyPresets = presets.Where(p => p.Triggers.Any(t => t.TriggerActive(triggerContext))).ToList(); @@ -692,5 +725,10 @@ private async Task HandleScreenSaverProcessAsync(IList processes, List< Logger.Error($"Screensaver check: can't power off: " + e.ToLogString()); } } + + public async Task ExecuteEventPresets(PresetTriggerType triggerType) + { + await ExecuteEventPresets(_serviceManager, new[] { triggerType }); + } } -} +} \ No newline at end of file diff --git a/ColorControl/XForms/ColorProfileViewModel.cs b/ColorControl/XForms/ColorProfileViewModel.cs index 223ba10..976ce03 100644 --- a/ColorControl/XForms/ColorProfileViewModel.cs +++ b/ColorControl/XForms/ColorProfileViewModel.cs @@ -7,9 +7,11 @@ using Microsoft.Win32; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Windows; using System.Windows.Forms; using WindowsDisplayAPI; @@ -112,7 +114,7 @@ internal class ColorProfileViewModel : BaseViewModel public string SelectedDisplayName => SelectedDisplay?.DisplayName; - public List ExistingProfiles { get; set; } + public ObservableCollection ExistingProfiles { get; set; } public string SelectedExistingProfile { get; set; } = CreateANewProfile; public string NewProfileName { get; set; } public string ProfileName => SelectedExistingProfile == CreateANewProfile ? NewProfileName : SelectedExistingProfile; @@ -120,6 +122,11 @@ internal class ColorProfileViewModel : BaseViewModel public Dictionary SaveOptions { get; } = Utils.EnumToDictionary(); public SaveOption SaveOption { get; set; } = SaveOption.InstallAndSetAsDefault; + public bool SDRSettingsEnabled { get; set; } + public Visibility VisibilityHDRSettings => IsHDR ? Visibility.Visible : Visibility.Collapsed; + public bool IsLoadEnabled { get; set; } + public bool SetMinMaxTml { get; set; } = true; + public bool PrimariesEnabled { get; set; } = true; public override string this[string columnName] { @@ -127,10 +134,26 @@ public override string this[string columnName] { var baseResult = base[columnName]; - if (columnName == "SelectedDisplay" || columnName == "PrimariesSource") + if (columnName == nameof(SelectedDisplay)) { UpdateDisplay(); } + else if (columnName == nameof(SDRTransferFunction)) + { + SDRSettingsEnabled = SDRTransferFunction == SDRTransferFunction.PurePower; + OnPropertyChanged(nameof(SDRSettingsEnabled)); + } + else if (columnName == nameof(SelectedExistingProfile)) + { + IsLoadEnabled = SelectedExistingProfile != CreateANewProfile; + OnPropertyChanged(nameof(IsLoadEnabled)); + } + else if (columnName == nameof(PrimariesSource)) + { + PrimariesEnabled = PrimariesSource == DisplayPrimariesSource.Custom; + OnPropertyChanged(nameof(PrimariesEnabled)); + UpdateDisplay(); + } return baseResult; } @@ -149,7 +172,7 @@ public ColorProfileViewModel(bool isHDR = true) RefreshDisplays(); - ExistingProfiles = new List { CreateANewProfile }; + ExistingProfiles = new ObservableCollection { CreateANewProfile }; NewProfileName = $"CC_{(isHDR ? "HDR" : "SDR")}_profile_{DateTime.Now:yyyyMMddHHmmss}.icm"; Description = $"CC_{(isHDR ? "HDR" : "SDR")}"; @@ -163,11 +186,16 @@ public ColorProfileViewModel(bool isHDR = true) public void RefreshDisplays() { - Displays = Display.GetDisplays().Select(d => new CustomDisplay(d)).ToList(); + Displays = Display.GetDisplays().Select(d => new CustomDisplay(d)) + .Where(d => IsHDR == d.IsHDRSupportedAndEnabled()) + .ToList(); + } - if (IsHDR) + public void Update() + { + foreach (var property in GetType().GetProperties().Where(p => !new[] { "Display", "PrimariesSource" }.Any(s => p.Name.Contains(s)))) { - Displays = Displays.Where(d => d.IsHDRSupportedAndEnabled()).ToList(); + OnPropertyChanged(property.Name); } } @@ -219,8 +247,14 @@ private void UpdateDisplay() } } - ExistingProfiles = new List { CreateANewProfile }; - ExistingProfiles.AddRange(CCD.GetDisplayColorProfiles(SelectedDisplay.DisplayName)); + ExistingProfiles = new ObservableCollection { CreateANewProfile }; + + var profiles = CCD.GetDisplayColorProfiles(SelectedDisplay.DisplayName); + + foreach (var profile in profiles) + { + ExistingProfiles.Add(profile); + } } private void GetEDID() diff --git a/ColorControl/XForms/ColorProfileWindow.xaml b/ColorControl/XForms/ColorProfileWindow.xaml index 7030df6..46066d0 100644 --- a/ColorControl/XForms/ColorProfileWindow.xaml +++ b/ColorControl/XForms/ColorProfileWindow.xaml @@ -7,7 +7,7 @@ Title="Create Color Profile" ResizeMode="CanResize" Width="773" - Height="775" + Height="791" WindowStartupLocation="CenterOwner" SnapsToDevicePixels="True" Style="{DynamicResource CustomWindowStyle}" @@ -19,58 +19,58 @@ -