From bc565abd382899415a844ecd77a5308f8ddcef62 Mon Sep 17 00:00:00 2001 From: Vincent Maas Date: Sat, 25 Nov 2023 11:47:52 +0100 Subject: [PATCH] fix auto-start not working, add support for color profiles, add B3 as supported models for service menu unlock --- ColorControl/ColorControl.csproj | 5 +- ColorControl/MainForm.cs | 3 +- ColorControl/Services/LG/LgPanel.Designer.cs | 6 +- ColorControl/Services/LG/LgPanel.cs | 2 +- ColorControl/Services/LG/LgPanel.resx | 62 ++- ColorControl/Services/NVIDIA/NvPanel.cs | 103 ++++- ColorControl/Services/NVIDIA/NvPreset.cs | 10 +- ColorControl/Services/NVIDIA/NvService.cs | 52 ++- .../Services/Samsung/SamTvConnection.cs | 6 +- .../Services/Samsung/SamsungService.cs | 2 +- .../NVIDIA/NvColorProfileSettings.cs | 25 ++ Shared/Native/CCD.cs | 399 +++++++++++++++++- Shared/Services/RpcServerService.cs | 19 +- novideo_srgb/DisplayConfigManager.cs | 7 +- novideo_srgb/ICCMatrixProfile.cs | 3 +- novideo_srgb/MainViewModel.cs | 8 +- novideo_srgb/MonitorData.cs | 67 +-- 17 files changed, 687 insertions(+), 92 deletions(-) create mode 100644 Shared/Contracts/NVIDIA/NvColorProfileSettings.cs diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index eb6152e..3efac85 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -18,8 +18,8 @@ Maassoft Maassoft 0 - 9.7.0.0 - 9.7.0.0 + 9.7.1.0 + 9.7.1.0 false true false @@ -38,6 +38,7 @@ Properties\app.manifest true ColorControl_TemporaryKey.pfx + false bin\x64\Debug\ diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index 015ddd8..a48f044 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -266,8 +266,9 @@ namespace ColorControl Logger.Debug("Initializing NVIDIA...Done."); var rpcService = _serviceProvider.GetRequiredService(); + var winAdminService = _serviceProvider.GetRequiredService(); - _nvPanel = new NvPanel(_serviceManager.NvService, _trayIcon, Handle, appContextProvider, rpcService); + _nvPanel = new NvPanel(_serviceManager.NvService, _trayIcon, Handle, appContextProvider, rpcService, winAdminService); _nvPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; InitShortcut(NvPanel.SHORTCUTID_NVQA, _config.NvQuickAccessShortcut, _serviceManager.NvService.ToggleQuickAccessForm); diff --git a/ColorControl/Services/LG/LgPanel.Designer.cs b/ColorControl/Services/LG/LgPanel.Designer.cs index 14645f7..b04a24c 100644 --- a/ColorControl/Services/LG/LgPanel.Designer.cs +++ b/ColorControl/Services/LG/LgPanel.Designer.cs @@ -418,19 +418,19 @@ mnuLgSetSvcMenuFlag.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miLgFullServiceMenuEnable, miLgFullServiceMenuDisable }); mnuLgSetSvcMenuFlag.Name = "mnuLgSetSvcMenuFlag"; mnuLgSetSvcMenuFlag.Size = new System.Drawing.Size(320, 22); - mnuLgSetSvcMenuFlag.Text = "Enable Full Service Menu (C3/G3 only)"; + mnuLgSetSvcMenuFlag.Text = "Enable Full Service Menu (B3/C3/G3 only)"; // // miLgFullServiceMenuEnable // miLgFullServiceMenuEnable.Name = "miLgFullServiceMenuEnable"; - miLgFullServiceMenuEnable.Size = new System.Drawing.Size(112, 22); + miLgFullServiceMenuEnable.Size = new System.Drawing.Size(180, 22); miLgFullServiceMenuEnable.Text = "Enable"; miLgFullServiceMenuEnable.Click += miLgFullServiceMenuEnable_Click; // // miLgFullServiceMenuDisable // miLgFullServiceMenuDisable.Name = "miLgFullServiceMenuDisable"; - miLgFullServiceMenuDisable.Size = new System.Drawing.Size(112, 22); + miLgFullServiceMenuDisable.Size = new System.Drawing.Size(180, 22); miLgFullServiceMenuDisable.Text = "Disable"; miLgFullServiceMenuDisable.Click += miLgFullServiceMenuDisable_Click; // diff --git a/ColorControl/Services/LG/LgPanel.cs b/ColorControl/Services/LG/LgPanel.cs index 551ac9e..41e5375 100644 --- a/ColorControl/Services/LG/LgPanel.cs +++ b/ColorControl/Services/LG/LgPanel.cs @@ -829,7 +829,7 @@ Do you want to continue?" mnuLgOLEDMotionPro.Visible = visible; miLgEnableMotionPro.Visible = enableVisible; - var svcMenuFlagVisible = new[] { "C3", "G3" }.Any(m => device?.ModelName?.Contains(m) == true); + var svcMenuFlagVisible = new[] { "B3", "C3", "G3" }.Any(m => device?.ModelName?.Contains(m) == true); mnuLgSetSvcMenuFlag.Visible = svcMenuFlagVisible; diff --git a/ColorControl/Services/LG/LgPanel.resx b/ColorControl/Services/LG/LgPanel.resx index 6a20818..203d97e 100644 --- a/ColorControl/Services/LG/LgPanel.resx +++ b/ColorControl/Services/LG/LgPanel.resx @@ -1,4 +1,64 @@ - + + + diff --git a/ColorControl/Services/NVIDIA/NvPanel.cs b/ColorControl/Services/NVIDIA/NvPanel.cs index 5656c19..b46d86b 100644 --- a/ColorControl/Services/NVIDIA/NvPanel.cs +++ b/ColorControl/Services/NVIDIA/NvPanel.cs @@ -38,8 +38,9 @@ namespace ColorControl.Services.NVIDIA private string _lastDisplayRefreshRates = string.Empty; private NotifyIcon _trayIcon; private RpcClientService _rpcService; + private readonly WinApiAdminService _winApiAdminService; - public NvPanel(NvService nvService, NotifyIcon trayIcon, IntPtr handle, AppContextProvider appContextProvider, RpcClientService rpcService) + public NvPanel(NvService nvService, NotifyIcon trayIcon, IntPtr handle, AppContextProvider appContextProvider, RpcClientService rpcService, WinApiAdminService winApiAdminService) { _nvService = nvService; _trayIcon = trayIcon; @@ -47,7 +48,7 @@ namespace ColorControl.Services.NVIDIA _appContextProvider = appContextProvider; _config = appContextProvider.GetAppContext().Config; _rpcService = rpcService; - + _winApiAdminService = winApiAdminService; _rpcService.Name = "NvService"; InitializeComponent(); @@ -441,6 +442,13 @@ namespace ColorControl.Services.NVIDIA FormUtils.BuildDropDownMenu(mnuNvOtherSettings, "Scaling", typeof(Scaling), preset, "scaling", nvPresetScalingMenuItem_Click, unchanged: true, skipValues: new object[] { Scaling.Customized }) .DropDownItems[0].Visible = !isCurrentDisplay; + var itemNameProfile = $"{mnuNvOtherSettings.Name}_Profile"; + var profileMenu = FormUtils.BuildMenuItem(mnuNvOtherSettings.DropDownItems, itemNameProfile, "Color Profile"); + + var subItemNameProfile = $"{itemNameProfile}_SubItem"; + var subItemProfile = FormUtils.BuildMenuItem(profileMenu.DropDownItems, subItemNameProfile, "", onClick: nvPresetColorProfileMenuItem_Click); + subItemProfile.Text = (!preset.ColorProfileSettings.ProfileName.IsNullOrWhiteSpace() ? $"{preset.ColorProfileSettings.ProfileName}" : "Unset") + " (click to change)"; + FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Content type", typeof(InfoFrameVideoContentType), preset.HdmiInfoFrameSettings, "ContentType", nvPresetHdmiContentTypeMenuItem_Click, unchanged: true) .DropDownItems[0].Visible = !isCurrentDisplay; FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Colorimetry", typeof(InfoFrameVideoColorimetry), preset.HdmiInfoFrameSettings, "Colorimetry", nvPresetHdmiColorimetryMenuItem_Click, unchanged: true) @@ -847,6 +855,70 @@ namespace ColorControl.Services.NVIDIA AddOrUpdateItem(); } + private void nvPresetColorProfileMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + + _nvService.ResolveDisplay(preset); + + if (preset.Display == null) + { + MessageForms.WarningOk($"Display {preset.displayName} not found"); + return; + } + + var display = preset.Display.Name; + if (!CCD.GetUsePerUserDisplayProfiles(display)) + { + if (MessageForms.QuestionYesNo("To be able to select a color profile, user specific settings have to be enabled. Do you want to enable this?") != DialogResult.Yes) + { + return; + } + + CCD.SetUsePerUserDisplayProfiles(display, true); + } + + var profiles = CCD.GetDisplayColorProfiles(display); + + if (!profiles.Any()) + { + if (MessageForms.QuestionYesNo("There are no profiles associated with this display. You have to manually add these through the Color Management app. Do you want to open this?") != DialogResult.Yes) + { + return; + } + + _winApiAdminService.StartProcess("colorcpl.exe"); + + return; + } + + profiles.Insert(0, "None"); + + var value = SelectStringValue(preset.ColorProfileSettings.ProfileName, "Color Profile", profiles); + + if (value == null) + { + return; + } + + if (value == "None") + { + value = null; + } + + if (preset.IsDisplayPreset) + { + _nvService.SetColorProfile(preset.Display, value); + + UpdateDisplayInfoItems(); + return; + } + + preset.ColorProfileSettings.ProfileName = value; + + AddOrUpdateItem(); + } + private void refreshRateMenuItem_Click(object sender, EventArgs e) { var refreshRate = (uint)((ToolStripItem)sender).Tag; @@ -1357,6 +1429,33 @@ namespace ColorControl.Services.NVIDIA return settingValue; } + private string SelectStringValue(string currentValue, string label, IEnumerable values) + { + var field = new FieldDefinition + { + Label = label, + FieldType = FieldType.DropDown, + Values = values, + Value = currentValue + }; + + var resultFields = MessageForms.ShowDialog(field.Label, new[] { field }); + + if (!resultFields.Any()) + { + return null; + } + + var strValue = (string)resultFields.First().Value; + + if (string.IsNullOrWhiteSpace(strValue)) + { + return null; + } + + return strValue; + } + private void miNvProfileInspector_Click(object sender, EventArgs e) { StartNvProfileInspector(); diff --git a/ColorControl/Services/NVIDIA/NvPreset.cs b/ColorControl/Services/NVIDIA/NvPreset.cs index 6eb0a77..fb87d36 100644 --- a/ColorControl/Services/NVIDIA/NvPreset.cs +++ b/ColorControl/Services/NVIDIA/NvPreset.cs @@ -4,6 +4,7 @@ using ColorControl.Shared.Contracts; using ColorControl.Shared.Contracts.NVIDIA; using Newtonsoft.Json; using nspector.Common; +using NStandard; using NvAPIWrapper.Display; using NvAPIWrapper.Native.Display; using System; @@ -43,9 +44,10 @@ namespace ColorControl.Services.NVIDIA public bool applyHdmiSettings { get; set; } public NvHdmiInfoFrameSettings HdmiInfoFrameSettings { get; set; } + public NvColorProfileSettings ColorProfileSettings { get; set; } [JsonIgnore] - public bool IsDisplayPreset => Display != null; + public bool IsDisplayPreset { get; set; } [JsonIgnore] public Display Display { get; set; } [JsonIgnore] @@ -72,6 +74,7 @@ namespace ColorControl.Services.NVIDIA driverSettings = new Dictionary(); ocSettings = new List(); HdmiInfoFrameSettings = new NvHdmiInfoFrameSettings(); + ColorProfileSettings = new NvColorProfileSettings(); } public NvPreset(ColorData colorData) : this() @@ -106,6 +109,7 @@ namespace ColorControl.Services.NVIDIA SDRBrightness = preset.SDRBrightness; scaling = preset.scaling; + ColorProfileSettings = new NvColorProfileSettings(preset.ColorProfileSettings); applyOther = preset.applyOther; HdmiInfoFrameSettings = new NvHdmiInfoFrameSettings(preset.HdmiInfoFrameSettings); @@ -279,6 +283,10 @@ namespace ColorControl.Services.NVIDIA { values.Add($"Scaling: {scaling.Value.GetDescription()}"); } + if (!ColorProfileSettings.ProfileName.IsNullOrWhiteSpace()) + { + values.Add($"Color profile: {ColorProfileSettings.ProfileName}"); + } if (!values.Any()) { diff --git a/ColorControl/Services/NVIDIA/NvService.cs b/ColorControl/Services/NVIDIA/NvService.cs index 8237fca..c80da57 100644 --- a/ColorControl/Services/NVIDIA/NvService.cs +++ b/ColorControl/Services/NVIDIA/NvService.cs @@ -28,6 +28,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using static ColorControl.Shared.Native.CCD; namespace ColorControl.Services.NVIDIA { @@ -241,15 +242,18 @@ namespace ColorControl.Services.NVIDIA } private void SetCurrentDisplay(NvPreset preset) + { + _currentDisplay = GetPresetDisplay(preset); + } + + private Display GetPresetDisplay(NvPreset preset) { if (preset.primaryDisplay) { - _currentDisplay = GetPrimaryDisplay(); - } - else - { - _currentDisplay = Display.GetDisplays().FirstOrDefault(x => x.Name.Equals(preset.displayName)); + return GetPrimaryDisplay(); } + + return Display.GetDisplays().FirstOrDefault(x => x.Name.Equals(preset.displayName)); } public Display GetPrimaryDisplay() @@ -346,6 +350,11 @@ namespace ColorControl.Services.NVIDIA { SetSDRBrightness(display, preset.SDRBrightness.Value); } + + if (preset.ColorProfileSettings.ProfileName != null) + { + CCD.SetDisplayDefaultColorProfile(display.Name, preset.ColorProfileSettings.ProfileName); + } } if (preset.applyHdmiSettings) @@ -903,9 +912,25 @@ namespace ColorControl.Services.NVIDIA return pathInfos.FirstOrDefault(c => c.TargetsInfo.Any(ti => ti.DisplayId == display.DisplayDevice.DisplayId)); } + public void SetColorProfile(Display display, string name) + { + CCD.SetDisplayDefaultColorProfile(display.Name, name); + } + public void TestResolution() { //var display = GetCurrentDisplay(); + + //CCD.GetUsePerUserDisplayProfiles(display.Name); + + //CCD.GetDisplayColorProfiles(display.Name); + + //CCD.InstallColorProfile(@"H:\srgb2000.icm"); + + //CCD.AddDisplayColorProfile(display.Name, "srgb2000.icm"); + + //CCD.SetDisplayDefaultColorProfile(display.Name, "srgb2000.icm"); + //var displayDevice = display.DisplayDevice; //var config = DisplayApi.GetDisplayConfig(); @@ -1071,13 +1096,16 @@ namespace ColorControl.Services.NVIDIA } var isPrimaryDisplay = displayCount == 1 || display.DisplayDevice.DisplayId == DisplayDevice.GetGDIPrimaryDisplayDevice()?.DisplayId; - var preset = new NvPreset { Display = display, applyColorData = false, primaryDisplay = isPrimaryDisplay }; + var preset = new NvPreset { Display = display, IsDisplayPreset = true, applyColorData = false, primaryDisplay = isPrimaryDisplay }; + + preset.HDREnabled = IsHDREnabled(display); preset.displayName = FormUtils.ExtendedDisplayName(display.Name); preset.colorData = GetCurrentColorData(display); preset.HdmiInfoFrameSettings = GetHdmiSettings(display); preset.SDRBrightness = GetSDRBrightness(display); preset.scaling = GetScaling(display); + preset.ColorProfileSettings.ProfileName = CCD.GetDisplayDefaultColorProfile(display.Name, preset.HDREnabled ? COLORPROFILESUBTYPE.CPST_EXTENDED_DISPLAY_COLOR_MODE : COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE); var mode = GetCurrentMode(display.Name); @@ -1100,8 +1128,6 @@ namespace ColorControl.Services.NVIDIA preset.ditheringMode = (uint)ditherInfo.mode; } - preset.HDREnabled = IsHDREnabled(display); - if (Config.ShowOverclocking) { var gpus = GetGPUs(); @@ -1311,5 +1337,15 @@ namespace ColorControl.Services.NVIDIA { Utils.WriteObject(_configFilename, Config); } + + internal void ResolveDisplay(NvPreset preset) + { + if (preset.Display != null) + { + return; + } + + preset.Display = GetPresetDisplay(preset); + } } } diff --git a/ColorControl/Services/Samsung/SamTvConnection.cs b/ColorControl/Services/Samsung/SamTvConnection.cs index 3ebebcb..6e50ba6 100644 --- a/ColorControl/Services/Samsung/SamTvConnection.cs +++ b/ColorControl/Services/Samsung/SamTvConnection.cs @@ -25,7 +25,7 @@ namespace ColorControl.Services.Samsung { public static SynchronizationContext SyncContext { get; set; } - public bool ConnectionClosed { get; private set; } + public bool? ConnectionClosed { get; private set; } public bool ClosedByDispose { get; private set; } private MessageWebSocket _connection; @@ -133,7 +133,7 @@ namespace ColorControl.Services.Samsung _tokens.TryAdd(id, taskSource); await SendMessageAsync(message); - if (ConnectionClosed) + if (ConnectionClosed == true) { throw new Exception("Connection closed"); } @@ -143,7 +143,7 @@ namespace ColorControl.Services.Samsung else { await SendMessageAsync(message); - if (ConnectionClosed) + if (ConnectionClosed == true) { throw new Exception("Connection closed"); } diff --git a/ColorControl/Services/Samsung/SamsungService.cs b/ColorControl/Services/Samsung/SamsungService.cs index 1f3dc55..8973b7f 100644 --- a/ColorControl/Services/Samsung/SamsungService.cs +++ b/ColorControl/Services/Samsung/SamsungService.cs @@ -205,7 +205,7 @@ namespace ColorControl.Services.Samsung } else { - var _ = SelectedDevice.ConnectAsync(); + //var _ = SelectedDevice.ConnectAsync(); } } } diff --git a/Shared/Contracts/NVIDIA/NvColorProfileSettings.cs b/Shared/Contracts/NVIDIA/NvColorProfileSettings.cs new file mode 100644 index 0000000..d178442 --- /dev/null +++ b/Shared/Contracts/NVIDIA/NvColorProfileSettings.cs @@ -0,0 +1,25 @@ +namespace ColorControl.Shared.Contracts.NVIDIA +{ + public class NvColorProfileSettings + { + public string ProfileName { get; set; } + + public NvColorProfileSettings() + { + } + + public NvColorProfileSettings(NvColorProfileSettings settings) + { + settings ??= new NvColorProfileSettings(); + + ProfileName = settings.ProfileName; + } + + public override string ToString() + { + var value = string.Empty; + + return value; + } + } +} diff --git a/Shared/Native/CCD.cs b/Shared/Native/CCD.cs index b238ebb..e3e5e0e 100644 --- a/Shared/Native/CCD.cs +++ b/Shared/Native/CCD.cs @@ -1,4 +1,5 @@ -using NWin32; +using ColorControl.Shared.Forms; +using NWin32; using System.ComponentModel; using System.Runtime.InteropServices; @@ -8,6 +9,7 @@ namespace ColorControl.Shared.Native public static class CCD { private static bool? IsHDRActive = null; + delegate (T, bool) DisplayConfigModeInfoDelegate(DisplayConfigModeInfo modeInfo); public enum DisplayTopology { @@ -60,7 +62,7 @@ namespace ColorControl.Shared.Native } } - public static void SetHDRState(bool enabled, string displayName = null, float? SDRWhiteLevelInNits = null) + public static void SetHDRState(bool enabled, string displayName = null) { uint pathCount, modeCount; @@ -120,7 +122,271 @@ namespace ColorControl.Shared.Native } } + public static LUID GetAdapterId(string displayName = null) + { + return ExecuteForModeConfig((modeInfo) => + { + if (modeInfo.infoType == DisplayConfigModeInfoType.Target) + { + return (modeInfo.adapterId, false); + } + + return (default(LUID), true); + }, displayName + ); + } + + public static uint GetSourceId(string displayName = null) + { + return ExecuteForModeConfig((modeInfo) => + { + if (modeInfo.infoType == DisplayConfigModeInfoType.Source) + { + return (modeInfo.id, false); + } + + return (0u, true); + }, displayName + ); + } + + public static bool InstallColorProfile(string profileName) + { + var result = NativeMethods.InstallColorProfileW(null, profileName); + + if (!result) + { + var err = NWin32.NativeMethods.GetLastError(); + + throw new Win32Exception((int)err); + } + + return true; + } + + private static bool MinRequiredVersion = true; + + public static bool AddDisplayColorProfile(string displayName, string profileName, bool setAsDefault = true) + { + var adapterId = GetAdapterId(displayName); + var sourceId = GetSourceId(displayName); + + var err = NativeMethods.ColorProfileAddDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, adapterId, sourceId, setAsDefault, true); + + if (err != NativeConstants.ERROR_SUCCESS) + { + throw new Win32Exception(err); + } + + return err == NativeConstants.ERROR_SUCCESS; + } + + public static bool RemoveDisplayColorProfile(string displayName, string profileName) + { + var adapterId = GetAdapterId(displayName); + var sourceId = GetSourceId(displayName); + + var err = NativeMethods.ColorProfileRemoveDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, adapterId, sourceId, true); + + if (err != NativeConstants.ERROR_SUCCESS) + { + throw new Win32Exception(err); + } + + return err == NativeConstants.ERROR_SUCCESS; + } + + public static bool SetDisplayDefaultColorProfile(string displayName, string profileName) + { + var adapterId = GetAdapterId(displayName); + var sourceId = GetSourceId(displayName); + + var subType = COLORPROFILESUBTYPE.CPST_PERCEPTUAL; + + if (profileName == null) + { + profileName = GetDisplayDefaultColorProfile(displayName, COLORPROFILESUBTYPE.CPST_EXTENDED_DISPLAY_COLOR_MODE); + + if (profileName == null) + { + return true; + } + + return RemoveDisplayColorProfile(displayName, profileName) && AddDisplayColorProfile(displayName, profileName, false); + } + + var err = NativeMethods.ColorProfileSetDisplayDefaultAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, COLORPROFILETYPE.CPT_ICC, subType, adapterId, sourceId); + + if (err != NativeConstants.ERROR_SUCCESS) + { + throw new Win32Exception(err); + } + + return err == NativeConstants.ERROR_SUCCESS; + } + + public static string GetDisplayDefaultColorProfile(string displayName, COLORPROFILESUBTYPE profileSubType = COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE) + { + if (!MinRequiredVersion) + { + return null; + } + + var adapterId = GetAdapterId(displayName); + var sourceId = GetSourceId(displayName); + + var scope = GetUsePerUserDisplayProfiles(displayName) ? WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER : WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE; + try + { + var pointer = new IntPtr(); + var err = NativeMethods.ColorProfileGetDisplayDefault(scope, adapterId, sourceId, COLORPROFILETYPE.CPT_ICC, profileSubType, pointer); + + if (err != NativeConstants.ERROR_SUCCESS) + { + // No default profile set + if (err == -2147024894) + { + return null; + } + + throw new Win32Exception(err); + } + var profileName = Marshal.PtrToStringUni(pointer); + + return profileName; + } + catch (Exception) + { + MinRequiredVersion = false; + } + return null; + } + + [StructLayout(LayoutKind.Sequential)] + public struct UnmanagedStruct + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public IntPtr[] listOfStrings; + } + + public static List GetDisplayColorProfiles(string displayName) + { + var adapterId = GetAdapterId(displayName); + var sourceId = GetSourceId(displayName); + + var pointer = IntPtr.Zero; + + var scope = GetUsePerUserDisplayProfiles(displayName) ? WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER : WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE; + + var err = NativeMethods.ColorProfileGetDisplayList(scope, adapterId, sourceId, pointer, out var profileCount); + + if (err != NativeConstants.ERROR_SUCCESS) + { + throw new Win32Exception(err); + } + + // No items + if (pointer == IntPtr.Zero) + { + return new List(); + } + + var data = Marshal.PtrToStructure(pointer); + var list = data.listOfStrings.Take((int)profileCount).Select(p => Marshal.PtrToStringUni(p)).ToList(); + + return list; + } + + public static bool GetUsePerUserDisplayProfiles(string displayName) + { + var deviceKey = GetDisplayDeviceKey(displayName); + + if (deviceKey == null) + { + return false; + } + + var result = NativeMethods.WcsGetUsePerUserProfiles(deviceKey, DeviceClassFlags.CLASS_MONITOR, out var usePerUserProfiles); + + if (!result) + { + //if (err != NativeConstants.ERROR_SUCCESS) + //{ + // throw new Win32Exception(err); + //} + } + + return usePerUserProfiles; + } + + public static bool SetUsePerUserDisplayProfiles(string displayName, bool usePerUserProfiles) + { + var deviceKey = GetDisplayDeviceKey(displayName); + + if (deviceKey == null) + { + return false; + } + + var result = NativeMethods.WcsSetUsePerUserProfiles(deviceKey, DeviceClassFlags.CLASS_MONITOR, usePerUserProfiles); + + return result; + } + + private static string GetDisplayDeviceKey(string displayName) + { + var hMonitor = FormUtils.GetMonitorForDisplayName(displayName); + + if (hMonitor == IntPtr.Zero) + { + return null; + } + + var mInfo = new MONITORINFOEX(); + mInfo.cbSize = (uint)Marshal.SizeOf(mInfo); + if (!NativeMethods.GetMonitorInfo(hMonitor, ref mInfo)) + { + return null; + } + + var dd = new DISPLAYDEVICE(); + dd.cb = (uint)Marshal.SizeOf(dd); + if (!NativeMethods.EnumDisplayDevices(mInfo.szDevice, 0, ref dd, 0)) + { + return null; + } + + return dd.DeviceKey; + } + public static uint GetSDRWhiteLevel(string displayName = null) + { + return ExecuteForModeConfig((modeInfo) => + { + if (modeInfo.infoType == DisplayConfigModeInfoType.Target) + { + var requestpacket = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO(); + requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER(); + requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; + requestpacket.header.size = Marshal.SizeOf(); + + requestpacket.header.adapterId = modeInfo.adapterId; + requestpacket.header.id = modeInfo.id; + + var err = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket); + + if (err == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorSupported) + { + return (GetSDRWhiteLevelForDisplayConfig(modeInfo), false); + } + } + + return (0, true); + }, displayName + ); + } + + private static T ExecuteForModeConfig(DisplayConfigModeInfoDelegate infoDelegate, string displayName = null) { uint pathCount, modeCount; @@ -142,24 +408,13 @@ namespace ColorControl.Shared.Native continue; } - var requestpacket = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO(); - requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER(); - requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; - requestpacket.header.size = Marshal.SizeOf(); - for (int i = 0; i < modeCount; i++) { - if (modesArray[i].infoType == DisplayConfigModeInfoType.Target) + var result = infoDelegate(modesArray[i]); + + if (!result.Item2) { - requestpacket.header.adapterId = modesArray[i].adapterId; - requestpacket.header.id = modesArray[i].id; - - err = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket); - - if (err == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorSupported) - { - return GetSDRWhiteLevelForDisplayConfig(modesArray[i]); - } + return result.Item1; } } } @@ -171,7 +426,7 @@ namespace ColorControl.Shared.Native throw new Win32Exception(err); } - return 0; + return default; } public static bool IsHDREnabled(string displayName = null) @@ -314,7 +569,7 @@ namespace ColorControl.Shared.Native } [StructLayout(LayoutKind.Sequential)] - struct LUID + public struct LUID { uint LowPart; uint HighPart; @@ -682,6 +937,87 @@ namespace ColorControl.Shared.Native public string viewGdiDeviceName; } + enum WCS_PROFILE_MANAGEMENT_SCOPE + { + WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE, + WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER + }; + + enum COLORPROFILETYPE + { + CPT_ICC, + CPT_DMP, + CPT_CAMP, + CPT_GMMP + }; + + public enum COLORPROFILESUBTYPE + { + CPST_PERCEPTUAL, + CPST_RELATIVE_COLORIMETRIC, + CPST_SATURATION, + CPST_ABSOLUTE_COLORIMETRIC, + CPST_NONE, + CPST_RGB_WORKING_SPACE, + CPST_CUSTOM_WORKING_SPACE, + CPST_STANDARD_DISPLAY_COLOR_MODE, + CPST_EXTENDED_DISPLAY_COLOR_MODE + }; + + enum DeviceClassFlags : UInt32 + { + // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h + /// + ///#define CLASS_MONITOR 'mntr' + /// + CLASS_MONITOR = 0x6d6e7472, + + /// + /// #define CLASS_PRINTER 'prtr' + /// + CLASS_PRINTER = 0x70727472, + + /// + /// #define CLASS_SCANNER 'scnr' + /// + CLASS_SCANNER = 0x73636e72 + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MONITORINFOEX + { + public uint cbSize; + public RECT rcMonitor; + public RECT rcWork; + public uint dwFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string szDevice; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct DISPLAYDEVICE + { + public uint cb; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string DeviceName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceString; + public uint StateFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceID; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string DeviceKey; + } + #endregion static class NativeMethods @@ -710,8 +1046,29 @@ namespace ColorControl.Shared.Native [DllImport("user32")] public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SDR_WHITE_LEVEL requestPacket); - [DllImport("user32")] - public static extern int DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SDR_WHITE_LEVEL setPacket); + [DllImport("mscms", CharSet = CharSet.Unicode)] + public static extern bool InstallColorProfileW(string machineName, string profilename); + + [DllImport("mscms", CharSet = CharSet.Unicode)] + public static extern int ColorProfileSetDisplayDefaultAssociation(WCS_PROFILE_MANAGEMENT_SCOPE scope, string profilename, COLORPROFILETYPE profiletype, COLORPROFILESUBTYPE profilesubtype, LUID targetadapterid, uint sourceid); + + [DllImport("mscms", CharSet = CharSet.Unicode)] + public static extern int ColorProfileAddDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE scope, string profilename, LUID targetadapterid, uint sourceid, bool setAsDefault, bool associateAsAdvancedColor); + + [DllImport("mscms", CharSet = CharSet.Unicode)] + public static extern int ColorProfileRemoveDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE scope, string profilename, LUID targetadapterid, uint sourceid, bool associateAsAdvancedColor); + [DllImport("mscms", CharSet = CharSet.Unicode)] + public static extern int ColorProfileGetDisplayList(WCS_PROFILE_MANAGEMENT_SCOPE scope, LUID targetadapterid, uint sourceid, in IntPtr pointer, out uint profileCount); + [DllImport("mscms", CharSet = CharSet.Unicode)] + public static extern int ColorProfileGetDisplayDefault(WCS_PROFILE_MANAGEMENT_SCOPE scope, LUID targetadapterid, uint sourceid, COLORPROFILETYPE profiletype, COLORPROFILESUBTYPE profilesubtype, in IntPtr pointer); + [DllImport("mscms", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool WcsGetUsePerUserProfiles(string deviceName, DeviceClassFlags deviceClass, out bool usePerUserProfiles); + [DllImport("mscms", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool WcsSetUsePerUserProfiles(string deviceName, DeviceClassFlags deviceClass, bool usePerUserProfiles); + [DllImport("user32.dll")] + public extern static bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAYDEVICE lpDisplayDevice, uint dwFlags); } } } diff --git a/Shared/Services/RpcServerService.cs b/Shared/Services/RpcServerService.cs index b2a29f7..5d0a2f4 100644 --- a/Shared/Services/RpcServerService.cs +++ b/Shared/Services/RpcServerService.cs @@ -34,8 +34,7 @@ public class RpcServerService var arg = message.Arguments[i]; var methodParam = methodParams[i]; - var convertedArg = ConvertArgument(arg, methodParam.ParameterType); - convertedArgs.Add(arg); + AddArgument(arg, methodParam.ParameterType, convertedArgs); } Logger.Debug($"Executing RPC: {message.ServiceName}.{message.MethodName}({string.Join(", ", convertedArgs)})"); @@ -54,21 +53,22 @@ public class RpcServerService } } - private object ConvertArgument(object arg, Type type) + private void AddArgument(object arg, Type type, List list) { if (arg.GetType() != type) { if (type.IsEnum) { - return Enum.ToObject(type, arg); + // Add enum directly to list and not return as 'object' + list.Add(Enum.ToObject(type, arg)); + return; } - var convertedArg = Convert.ChangeType(arg, type); - return convertedArg; + list.Add(Convert.ChangeType(arg, type)); + return; } - return arg; - + list.Add(arg); } public async Task ExecuteRpcTypedAsync(SvcRpcMessageTyped message) @@ -92,8 +92,7 @@ public class RpcServerService var arg = prop.GetValue(message); var methodParam = methodParams[paramIndex]; - var convertedArg = ConvertArgument(arg, methodParam.ParameterType); - convertedArgs.Add(arg); + AddArgument(arg, methodParam.ParameterType, convertedArgs); paramIndex++; } diff --git a/novideo_srgb/DisplayConfigManager.cs b/novideo_srgb/DisplayConfigManager.cs index 7b5b255..082521b 100644 --- a/novideo_srgb/DisplayConfigManager.cs +++ b/novideo_srgb/DisplayConfigManager.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; using System.Runtime.InteropServices; namespace novideo_srgb @@ -15,7 +12,7 @@ namespace novideo_srgb private static extern int QueryDisplayConfig(QDC flags, ref int numPathArrayElements, [In, Out] DISPLAYCONFIG_PATH_INFO[] pathArray, ref int numModeInfoArrayElements, [In, Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, out DISPLAYCONFIG_TOPOLOGY_ID currentTopologyId); [DllImport("user32")] - private static extern int QueryDisplayConfig(QDC flags, ref int numPathArrayElements, [In, Out] DISPLAYCONFIG_PATH_INFO[] pathArray, ref int numModeInfoArrayElements, [In, Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, IntPtr currentTopologyId); + private static extern int QueryDisplayConfig(QDC flags, ref int numPathArrayElements, [In, Out] DISPLAYCONFIG_PATH_INFO[] path4Array, ref int numModeInfoArrayElements, [In, Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, IntPtr currentTopologyId); [DllImport("user32")] private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO requestPacket); diff --git a/novideo_srgb/ICCMatrixProfile.cs b/novideo_srgb/ICCMatrixProfile.cs index 18c65b7..ba5f4aa 100644 --- a/novideo_srgb/ICCMatrixProfile.cs +++ b/novideo_srgb/ICCMatrixProfile.cs @@ -1,5 +1,4 @@ -using System; -using System.IO; +using System.IO; namespace novideo_srgb { diff --git a/novideo_srgb/MainViewModel.cs b/novideo_srgb/MainViewModel.cs index f4e0a1b..08de11c 100644 --- a/novideo_srgb/MainViewModel.cs +++ b/novideo_srgb/MainViewModel.cs @@ -37,11 +37,15 @@ namespace novideo_srgb foreach (var display in Display.GetDisplays()) { var displays = WindowsDisplayAPI.Display.GetDisplays(); - var path = displays.First(x => x.DisplayName == display.Name).DevicePath; + var path = displays.FirstOrDefault(x => x.DisplayName == display.Name)?.DevicePath; + if (path == null) + { + continue; + } var hdrActive = hdrPaths.Contains(path); - var settings = config?.FirstOrDefault(x => (string)x.Attribute("path") == path); + var settings = config?.FirstOrDefault(x => (string?)x.Attribute("path") == path); MonitorData monitor; if (settings != null) { diff --git a/novideo_srgb/MonitorData.cs b/novideo_srgb/MonitorData.cs index c789525..6b5e846 100644 --- a/novideo_srgb/MonitorData.cs +++ b/novideo_srgb/MonitorData.cs @@ -6,6 +6,7 @@ using NvAPIWrapper.Display; using NvAPIWrapper.GPU; using System.ComponentModel; using System.Runtime.CompilerServices; +using static novideo_srgb.Colorimetry; namespace novideo_srgb { @@ -87,49 +88,57 @@ namespace novideo_srgb _clamped = Novideo.IsColorSpaceConversionActive(_output); } - if (!doClamp) return; + if (!doClamp) + { + return; + } if (_clamped) Thread.Sleep(100); if (UseEdid) Novideo.SetColorSpaceConversion(_output, Colorimetry.RGBToRGB(TargetColorSpace, EdidColorSpace)); else if (UseIcc) { - var profile = ICCMatrixProfile.FromFile(ProfilePath); - if (CalibrateGamma) + SetICCProfile(ProfilePath, SelectedGamma, TargetColorSpace); + } + } + + private void SetICCProfile(string fileName, int selectedGamma, ColorSpace colorSpace) + { + var profile = ICCMatrixProfile.FromFile(fileName); + if (CalibrateGamma) + { + var trcBlack = Matrix.FromValues(new[,] { - var trcBlack = Matrix.FromValues(new[,] - { { profile.trcs[0].SampleAt(0) }, { profile.trcs[1].SampleAt(0) }, { profile.trcs[2].SampleAt(0) } }); - var black = (profile.matrix * trcBlack)[1]; + var black = (profile.matrix * trcBlack)[1]; - ToneCurve gamma; - switch (SelectedGamma) - { - case 0: - gamma = new SrgbEOTF(black); - break; - case 1: - gamma = new GammaToneCurve(2.4, black, 0); - break; - case 2: - gamma = new GammaToneCurve(CustomGamma, black, CustomPercentage / 100); - break; - case 3: - gamma = new GammaToneCurve(CustomGamma, black, CustomPercentage / 100, true); - break; - default: - throw new NotSupportedException("Unsupported gamma type " + SelectedGamma); - } - - Novideo.SetColorSpaceConversion(_output, profile, TargetColorSpace, gamma, DisableOptimization); - } - else + ToneCurve gamma; + switch (selectedGamma) { - Novideo.SetColorSpaceConversion(_output, profile, TargetColorSpace); + case 0: + gamma = new SrgbEOTF(black); + break; + case 1: + gamma = new GammaToneCurve(2.4, black, 0); + break; + case 2: + gamma = new GammaToneCurve(CustomGamma, black, CustomPercentage / 100); + break; + case 3: + gamma = new GammaToneCurve(CustomGamma, black, CustomPercentage / 100, true); + break; + default: + throw new NotSupportedException("Unsupported gamma type " + selectedGamma); } + + Novideo.SetColorSpaceConversion(_output, profile, colorSpace, gamma, DisableOptimization); + } + else + { + Novideo.SetColorSpaceConversion(_output, profile, colorSpace); } }