diff --git a/ColorControl/AmdPreset.cs b/ColorControl/AmdPreset.cs index 8158b4c..3079152 100644 --- a/ColorControl/AmdPreset.cs +++ b/ColorControl/AmdPreset.cs @@ -19,7 +19,7 @@ namespace ColorControl public bool primaryDisplay { get; set; } public string displayName { get; set; } - public AmdPreset() + public AmdPreset() : base() { applyColorData = true; applyDithering = false; diff --git a/ColorControl/CCD.cs b/ColorControl/CCD.cs new file mode 100644 index 0000000..4f58eab --- /dev/null +++ b/ColorControl/CCD.cs @@ -0,0 +1,564 @@ +using NWin32; +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace ColorControl +{ + // This class takes care of wrapping "Connecting and Configuring Displays(CCD) Win32 API" + internal static class CCD + { + public enum DisplayTopology + { + Internal, + External, + Extend, + Clone + } + + public static DisplayTopology GetDisplayTopology() + { + uint numPathArrayElements; + uint numModeInfoArrayElements; + + NativeMethods.GetDisplayConfigBufferSizes(QueryDisplayFlags.DatabaseCurrent, out numPathArrayElements, out numModeInfoArrayElements); + + var pathArray = new DisplayConfigPathInfo[numPathArrayElements]; + var modeArray = new DisplayConfigModeInfo[numModeInfoArrayElements]; + + DisplayConfigTopologyId displayTopology; + + NativeMethods.QueryDisplayConfig(QueryDisplayFlags.DatabaseCurrent, ref numPathArrayElements, pathArray, ref numModeInfoArrayElements, modeArray, out displayTopology); + + switch (displayTopology) + { + case DisplayConfigTopologyId.External: return DisplayTopology.External; + case DisplayConfigTopologyId.Internal: return DisplayTopology.Internal; + case DisplayConfigTopologyId.Extend: return DisplayTopology.Extend; + } + + return DisplayTopology.Clone; + } + + public static void SetDisplayTopology(DisplayTopology displayTopology) + { + switch (displayTopology) + { + case DisplayTopology.External: + NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyExternal)); + break; + case DisplayTopology.Internal: + NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyInternal)); + break; + case DisplayTopology.Extend: + NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyExtend)); + break; + case DisplayTopology.Clone: + NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyClone)); + break; + } + } + + public static void SetHDRState(bool enabled, string displayName = null) + { + uint pathCount, modeCount; + + var err = NativeMethods.GetDisplayConfigBufferSizes(QueryDisplayFlags.OnlyActivePaths, out pathCount, out modeCount); + if (err == NativeConstants.ERROR_SUCCESS) + { + var pathsArray = new DisplayConfigPathInfo[pathCount]; + var modesArray = new DisplayConfigModeInfo[modeCount]; + + DisplayConfigTopologyId displayTopology; + + err = NativeMethods.QueryDisplayConfig(QueryDisplayFlags.DatabaseCurrent, ref pathCount, pathsArray, ref modeCount, modesArray, out displayTopology); + if (err == NativeConstants.ERROR_SUCCESS) + { + foreach (var path in pathsArray) + { + // get display name + var info = new DISPLAYCONFIG_SOURCE_DEVICE_NAME(); + info.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + info.header.size = Marshal.SizeOf(); + info.header.adapterId = path.sourceInfo.adapterId; + info.header.id = path.sourceInfo.id; + + err = NativeMethods.DisplayConfigGetDeviceInfo(ref info); + if (err != NativeConstants.ERROR_SUCCESS) + { + break; + } + + var deviceName = info.viewGdiDeviceName; + if (displayName != null && !EqualDisplayNames(deviceName, displayName)) + { + continue; + } + + var setpacket = new DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO(); + setpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER(); + setpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; + setpacket.header.size = Marshal.SizeOf(); ; + + 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) + { + setpacket.header.adapterId = modesArray[i].adapterId; + setpacket.header.id = modesArray[i].id; + requestpacket.header.adapterId = modesArray[i].adapterId; + requestpacket.header.id = modesArray[i].id; + + if (NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket) == NativeConstants.ERROR_SUCCESS) + { + if (requestpacket.advancedColorSupported) + { + setpacket.enableAdvancedColor = enabled ? 1U : 0; + NativeMethods.DisplayConfigSetDeviceInfo(ref setpacket); + } + } + } + } + } + } + } + + if (err != NativeConstants.ERROR_SUCCESS) + { + throw new Win32Exception(err); + } + } + + private static bool EqualDisplayNames(string displayName1, string displayName2) + { + displayName1 = displayName1.Replace("\\", string.Empty); + displayName2 = displayName2.Replace("\\", string.Empty); + + return displayName1.Equals(displayName2, StringComparison.OrdinalIgnoreCase); + } + + private enum DISPLAYCONFIG_MODE_INFO_TYPE + { + DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1, + DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2, + DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3, + } + + private enum DISPLAYCONFIG_DEVICE_INFO_TYPE + { + DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1, + DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2, + DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3, + DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4, + DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5, + DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6, + DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION = 7, + DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION = 8, + DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9, + DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10, + DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11, + } + + [StructLayout(LayoutKind.Sequential)] + private struct DISPLAYCONFIG_DEVICE_INFO_HEADER + { + public DISPLAYCONFIG_DEVICE_INFO_TYPE type; + public int size; + public LUID adapterId; + public uint id; + } + + [StructLayout(LayoutKind.Sequential)] + struct LUID + { + uint LowPart; + uint HighPart; + } + + [Flags] + enum DisplayConfigVideoOutputTechnology : uint + { + Other = 4294967295, // -1 + Hd15 = 0, + Svideo = 1, + CompositeVideo = 2, + ComponentVideo = 3, + Dvi = 4, + Hdmi = 5, + Lvds = 6, + DJpn = 8, + Sdi = 9, + DisplayportExternal = 10, + DisplayportEmbedded = 11, + UdiExternal = 12, + UdiEmbedded = 13, + Sdtvdongle = 14, + Internal = 0x80000000, + ForceUint32 = 0xFFFFFFFF + } + + #region SdcFlags enum + + [Flags] + enum SdcFlags : uint + { + Zero = 0, + + TopologyInternal = 0x00000001, + TopologyClone = 0x00000002, + TopologyExtend = 0x00000004, + TopologyExternal = 0x00000008, + TopologySupplied = 0x00000010, + + UseSuppliedDisplayConfig = 0x00000020, + Validate = 0x00000040, + Apply = 0x00000080, + NoOptimization = 0x00000100, + SaveToDatabase = 0x00000200, + AllowChanges = 0x00000400, + PathPersistIfRequired = 0x00000800, + ForceModeEnumeration = 0x00001000, + AllowPathOrderChanges = 0x00002000, + + UseDatabaseCurrent = TopologyInternal | TopologyClone | TopologyExtend | TopologyExternal + } + + [Flags] + enum DisplayConfigFlags : uint + { + Zero = 0x0, + PathActive = 0x00000001 + } + + [Flags] + enum DisplayConfigSourceStatus + { + Zero = 0x0, + InUse = 0x00000001 + } + + [Flags] + enum DisplayConfigTargetStatus : uint + { + Zero = 0x0, + + InUse = 0x00000001, + FORCIBLE = 0x00000002, + ForcedAvailabilityBoot = 0x00000004, + ForcedAvailabilityPath = 0x00000008, + ForcedAvailabilitySystem = 0x00000010, + } + + [Flags] + enum DisplayConfigRotation : uint + { + Zero = 0x0, + + Identity = 1, + Rotate90 = 2, + Rotate180 = 3, + Rotate270 = 4, + ForceUint32 = 0xFFFFFFFF + } + + [Flags] + enum DisplayConfigPixelFormat : uint + { + Zero = 0x0, + + Pixelformat8Bpp = 1, + Pixelformat16Bpp = 2, + Pixelformat24Bpp = 3, + Pixelformat32Bpp = 4, + PixelformatNongdi = 5, + PixelformatForceUint32 = 0xffffffff + } + + [Flags] + enum DisplayConfigScaling : uint + { + Zero = 0x0, + + Identity = 1, + Centered = 2, + Stretched = 3, + Aspectratiocenteredmax = 4, + Custom = 5, + Preferred = 128, + ForceUint32 = 0xFFFFFFFF + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfigRational + { + uint numerator; + uint denominator; + } + + [Flags] + enum DisplayConfigScanLineOrdering : uint + { + Unspecified = 0, + Progressive = 1, + Interlaced = 2, + InterlacedUpperfieldfirst = Interlaced, + InterlacedLowerfieldfirst = 3, + ForceUint32 = 0xFFFFFFFF + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfigPathInfo + { + public DisplayConfigPathSourceInfo sourceInfo; + public DisplayConfigPathTargetInfo targetInfo; + public uint flags; + } + + [Flags] + enum DisplayConfigModeInfoType : uint + { + Zero = 0, + + Source = 1, + Target = 2, + ForceUint32 = 0xFFFFFFFF + } + + [StructLayout(LayoutKind.Explicit)] + struct DisplayConfigModeInfo + { + [FieldOffset(0)] + public DisplayConfigModeInfoType infoType; + + [FieldOffset(4)] + public uint id; + + [FieldOffset(8)] + public LUID adapterId; + + [FieldOffset(16)] + public DisplayConfigTargetMode targetMode; + + [FieldOffset(16)] + public DisplayConfigSourceMode sourceMode; + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfig2DRegion + { + uint cx; + uint cy; + } + + [Flags] + enum D3DmdtVideoSignalStandard : uint + { + Uninitialized = 0, + VesaDmt = 1, + VesaGtf = 2, + VesaCvt = 3, + Ibm = 4, + Apple = 5, + NtscM = 6, + NtscJ = 7, + Ntsc443 = 8, + PalB = 9, + PalB1 = 10, + PalG = 11, + PalH = 12, + PalI = 13, + PalD = 14, + PalN = 15, + PalNc = 16, + SecamB = 17, + SecamD = 18, + SecamG = 19, + SecamH = 20, + SecamK = 21, + SecamK1 = 22, + SecamL = 23, + SecamL1 = 24, + Eia861 = 25, + Eia861A = 26, + Eia861B = 27, + PalK = 28, + PalK1 = 29, + PalL = 30, + PalM = 31, + Other = 255 + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfigVideoSignalInfo + { + long pixelRate; + DisplayConfigRational hSyncFreq; + DisplayConfigRational vSyncFreq; + DisplayConfig2DRegion activeSize; + DisplayConfig2DRegion totalSize; + + D3DmdtVideoSignalStandard videoStandard; + DisplayConfigScanLineOrdering ScanLineOrdering; + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfigTargetMode + { + DisplayConfigVideoSignalInfo targetVideoSignalInfo; + } + + [StructLayout(LayoutKind.Sequential)] + struct PointL + { + int x; + int y; + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfigSourceMode + { + uint width; + uint height; + DisplayConfigPixelFormat pixelFormat; + PointL position; + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfigPathSourceInfo + { + public LUID adapterId; + public uint id; + public uint modeInfoIdx; + + public DisplayConfigSourceStatus statusFlags; + } + + [StructLayout(LayoutKind.Sequential)] + struct DisplayConfigPathTargetInfo + { + public LUID adapterId; + public uint id; + public uint modeInfoIdx; + public DisplayConfigVideoOutputTechnology outputTechnology; + public DisplayConfigRotation rotation; + public DisplayConfigScaling scaling; + public DisplayConfigRational refreshRate; + public DisplayConfigScanLineOrdering scanLineOrdering; + + public bool targetAvailable; + public DisplayConfigTargetStatus statusFlags; + } + + [Flags] + enum QueryDisplayFlags : uint + { + Zero = 0x0, + + AllPaths = 0x00000001, + OnlyActivePaths = 0x00000002, + DatabaseCurrent = 0x00000004 + } + + [Flags] + enum DisplayConfigTopologyId : uint + { + Zero = 0x0, + + Internal = 0x00000001, + Clone = 0x00000002, + Extend = 0x00000004, + External = 0x00000008, + ForceUint32 = 0xFFFFFFFF + } + + private enum DISPLAYCONFIG_COLOR_ENCODING + { + DISPLAYCONFIG_COLOR_ENCODING_RGB = 0, + DISPLAYCONFIG_COLOR_ENCODING_YCBCR444 = 1, + DISPLAYCONFIG_COLOR_ENCODING_YCBCR422 = 2, + DISPLAYCONFIG_COLOR_ENCODING_YCBCR420 = 3, + DISPLAYCONFIG_COLOR_ENCODING_INTENSITY = 4, + } + + [StructLayout(LayoutKind.Sequential)] + private struct DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO + { + public DISPLAYCONFIG_DEVICE_INFO_HEADER header; + public uint value; + public DISPLAYCONFIG_COLOR_ENCODING colorEncoding; + public int bitsPerColorChannel; + + public bool advancedColorSupported => (value & 0x1) == 0x1; + public bool advancedColorEnabled => (value & 0x2) == 0x2; + public bool wideColorEnforced => (value & 0x4) == 0x4; + public bool advancedColorForceDisabled => (value & 0x8) == 0x8; + } + + [StructLayout(LayoutKind.Sequential)] + private struct DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO + { + public DISPLAYCONFIG_DEVICE_INFO_HEADER header; + public uint enableAdvancedColor; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS + { + public uint value; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct DISPLAYCONFIG_TARGET_DEVICE_NAME + { + public DISPLAYCONFIG_DEVICE_INFO_HEADER header; + public DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags; + public DisplayConfigVideoOutputTechnology outputTechnology; + public ushort edidManufactureId; + public ushort edidProductCodeId; + public uint connectorInstance; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string monitorFriendlyDeviceName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string monitorDevicePat; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct DISPLAYCONFIG_SOURCE_DEVICE_NAME + { + public DISPLAYCONFIG_DEVICE_INFO_HEADER header; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string viewGdiDeviceName; + } + + #endregion + + static class NativeMethods + { + [DllImport("User32.dll")] + public static extern int GetDisplayConfigBufferSizes(QueryDisplayFlags flags, out uint numPathArrayElements, out uint numModeInfoArrayElements); + + [DllImport("User32.dll")] + public static extern int SetDisplayConfig(uint numPathArrayElements, [In] DisplayConfigPathInfo[] pathArray, uint numModeInfoArrayElements, [In] DisplayConfigModeInfo[] modeInfoArray, SdcFlags flags); + + [DllImport("User32.dll")] + public static extern int QueryDisplayConfig(QueryDisplayFlags flags, ref uint numPathArrayElements, [In, Out] DisplayConfigPathInfo[] pathInfoArray, ref uint modeInfoArrayElements, [In, Out] DisplayConfigModeInfo[] modeInfoArray, out DisplayConfigTopologyId id); + + [DllImport("user32")] + public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO requestPacket); + + [DllImport("user32")] + public static extern int DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO setPacket); + + [DllImport("user32")] + public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_TARGET_DEVICE_NAME requestPacket); + + [DllImport("user32")] + public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DEVICE_NAME requestPacket); + } + } +} diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index 47b5011..c4c19ba 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -27,7 +27,7 @@ ColorControl Maassoft 0 - 3.3.0.0 + 4.0.0.0 false true true @@ -74,10 +74,10 @@ true - 482C987E8C69872608C9D44751E004C5784E8540 + CF0BC153EA664CD97AC6F234B91F45D34630958F - ColorControl_TemporaryKey.pfx + ColorControl_1_TemporaryKey.pfx true @@ -104,23 +104,23 @@ ..\packages\TaskScheduler.2.9.1\lib\net452\Microsoft.Win32.TaskScheduler.dll - - ..\packages\Native.0.1.0\lib\net35\Native.dll + + ..\packages\Native.0.1.1\lib\net46\Native.dll ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - ..\packages\NLog.4.7.9\lib\net45\NLog.dll + ..\packages\NLog.4.7.10\lib\net45\NLog.dll - - ..\packages\NStandard.0.6.12.5\lib\net46\NStandard.dll + + ..\packages\NStandard.0.8.9\lib\net46\NStandard.dll ..\packages\NvAPIWrapper.Net.0.8.1.101\lib\net45\NvAPIWrapper.dll - - ..\packages\NWin32.1.0.7.6\lib\net46\NWin32.dll + + ..\packages\NWin32.1.2.0\lib\net46\NWin32.dll ..\packages\PacketDotNet.1.2.0\lib\net45\PacketDotNet.dll @@ -177,6 +177,7 @@ + True @@ -239,6 +240,7 @@ + MainForm.cs diff --git a/ColorControl/LgDevice.cs b/ColorControl/LgDevice.cs index 543ef12..fbb92f6 100644 --- a/ColorControl/LgDevice.cs +++ b/ColorControl/LgDevice.cs @@ -13,7 +13,7 @@ namespace ColorControl { public class InvokableAction { - public Func, bool> Function { get; set; } + public Func, bool> Function { get; set; } public string Name { get; set; } public Type EnumType { get; set; } public decimal MinValue { get; set; } @@ -21,6 +21,12 @@ namespace ColorControl public string Category { get; set; } } + public class LgDevicePictureSettings + { + public int Backlight { get; set; } + public int Contrast { get; set; } + } + public enum PowerState { Unknown, @@ -69,20 +75,27 @@ namespace ColorControl private DateTimeOffset _poweredOffViaAppDateTime { get; set; } [JsonIgnore] private List _invokableActions = new List(); + [JsonIgnore] + private SemaphoreSlim _connectSemaphore = new SemaphoreSlim(1, 1); [JsonIgnore] public string ModelName { get; private set; } + [JsonIgnore] + public LgDevicePictureSettings PictureSettings { get; private set; } + [JsonConstructor] public LgDevice(string name, string ipAddress, string macAddress, bool isCustom = true, bool isDummy = false) { + PictureSettings = new LgDevicePictureSettings(); + Name = name; IpAddress = ipAddress; MacAddress = macAddress; IsCustom = isCustom; IsDummy = isDummy; - AddInvokableAction("WOL", new Func, bool>(WakeAction)); + AddInvokableAction("WOL", new Func, bool>(WakeAction)); AddGenericPictureAction("backlight", minValue: 0, maxValue: 100); AddGenericPictureAction("brightness", minValue: 0, maxValue: 100); AddGenericPictureAction("contrast", minValue: 0, maxValue: 100); @@ -90,9 +103,14 @@ namespace ColorControl AddGenericPictureAction("pictureMode", typeof(PictureMode)); AddGenericPictureAction("colorGamut", typeof(ColorGamut)); AddGenericPictureAction("dynamicContrast", typeof(OffToAuto)); + //AddGenericPictureAction("dynamicColor", typeof(OffToAuto)); + //AddGenericPictureAction("superResolution", typeof(OffToAuto)); AddGenericPictureAction("peakBrightness", typeof(OffToAuto)); AddGenericPictureAction("smoothGradation", typeof(OffToAuto)); AddGenericPictureAction("energySaving", typeof(EnergySaving)); + AddGenericPictureAction("hdrDynamicToneMapping", typeof(DynamicTonemapping)); + //AddGenericPictureAction("blackLevel", typeof(LowToAuto)); + //AddGenericPictureAction("ambientLightCompensation", typeof(OffToAuto2)); //AddGenericPictureAction("truMotionMode", typeof(TruMotionMode)); AddGenericPictureAction("motionProOLED", typeof(OffToHigh)); AddGenericPictureAction("uhdDeepColorHDMI1", typeof(OffToOn), category: "other"); @@ -104,9 +122,10 @@ namespace ColorControl AddGenericPictureAction("gameOptimizationHDMI2", typeof(OffToOn), category: "other"); AddGenericPictureAction("gameOptimizationHDMI3", typeof(OffToOn), category: "other"); AddGenericPictureAction("gameOptimizationHDMI4", typeof(OffToOn), category: "other"); + AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50); } - private void AddInvokableAction(string name, Func, bool> function) + private void AddInvokableAction(string name, Func, bool> function) { var action = new InvokableAction { @@ -122,7 +141,7 @@ namespace ColorControl var action = new InvokableAction { Name = name, - Function = new Func, bool>(GenericPictureAction), + Function = new Func, bool>(GenericPictureAction), EnumType = type, MinValue = minValue, MaxValue = maxValue, @@ -140,36 +159,56 @@ namespace ColorControl public async Task Connect(int retries = 3) { + var locked = _connectSemaphore.CurrentCount == 0; + await _connectSemaphore.WaitAsync(); try { - DisposeConnection(); - _lgTvApi = await LgTvApi.CreateLgTvApi(IpAddress, retries); - - //Test(); - //_lgTvApi.Test3(); - if (_lgTvApi != null) + if (locked && _lgTvApi != null) { - var info = await _lgTvApi.GetSystemInfo("modelName"); - if (info != null) + return true; + } + + try + { + DisposeConnection(); + _lgTvApi = await LgTvApi.CreateLgTvApi(IpAddress, retries); + + //Test(); + //_lgTvApi.Test3(); + if (_lgTvApi != null) { - ModelName = info.modelName; - } + var info = await _lgTvApi.GetSystemInfo("modelName"); + if (info != null) + { + ModelName = info.modelName; + } + + //await _lgTvApi.SubscribeVolume(VolumeChanged); + await _lgTvApi.SubscribePowerState(PowerStateChanged); + await _lgTvApi.SubscribePictureSettings(PictureSettingsChanged); + + //var result = await GetPictureSettings(); - //await _lgTvApi.SubscribeVolume(VolumeChanged); - await _lgTvApi.SubscribePowerState(PowerStateChanged); + //await _lgTvApi.SetSystemSettings("adjustingLuminance", new[] { 0, 0, -5, -10, -15, -20, -25, -30, -35, -40, -45, -50, -50, -50, -40, -30, -20, -10, 10, 20, 30, 50 }); + //await _lgTvApi.SetSystemSettings("adjustingLuminance", new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); - //await _lgTvApi.SetInput("HDMI_1"); - //await Task.Delay(2000); - //await _lgTvApi.SetConfig("com.palm.app.settings.enableHdmiPcLabel", true); - //await _lgTvApi.SetInput("HDMI_2"); + //await _lgTvApi.SetInput("HDMI_1"); + //await Task.Delay(2000); + //await _lgTvApi.SetConfig("com.palm.app.settings.enableHdmiPcLabel", true); + //await _lgTvApi.SetInput("HDMI_2"); + } + return _lgTvApi != null; + } + catch (Exception ex) + { + string logMessage = ex.ToLogString(Environment.StackTrace); + Logger.Error($"Error while connecting to {IpAddress}: {logMessage}"); + return false; } - return _lgTvApi != null; } - catch (Exception ex) + finally { - string logMessage = ex.ToLogString(Environment.StackTrace); - Logger.Error($"Error while connecting to {IpAddress}: {logMessage}"); - return false; + _connectSemaphore.Release(); } } @@ -177,6 +216,26 @@ namespace ColorControl { return true; } + + public bool PictureSettingsChanged(dynamic payload) + { + if (payload.settings != null) + { + var settings = payload.settings; + + if (settings.backlight != null) + { + PictureSettings.Backlight = int.Parse(settings.backlight.Value); + } + if (settings.contrast != null) + { + PictureSettings.Contrast = int.Parse(settings.contrast.Value); + } + } + + return true; + } + public bool PowerStateChanged(dynamic payload) { Logger.Debug($"[{Name}] Power state change: {JsonConvert.SerializeObject(payload)}"); @@ -193,6 +252,7 @@ namespace ColorControl if (payload.processing == null && (DateTimeOffset.Now - _poweredOffViaAppDateTime).TotalMilliseconds > 500) { PoweredOffViaApp = false; + PoweredOffBy = PowerOffSource.Unknown; _poweredOffViaAppDateTime = DateTimeOffset.MinValue; } } @@ -313,7 +373,7 @@ namespace ColorControl { var keyValue = keySpec[0].Split('('); key = keyValue[0]; - parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Split(','); + parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Split(';'); } var action = _invokableActions.FirstOrDefault(a => a.Name.Equals(key, StringComparison.OrdinalIgnoreCase)); if (action != null) @@ -350,9 +410,9 @@ namespace ColorControl if (parameters?.Length > 0) { - var keyValues = new Dictionary { + var keyValues = new Dictionary { { "name", action.Name }, - { "value", parameters[0] }, + { "value", parameters }, { "category", action.Category } }; @@ -551,6 +611,12 @@ namespace ColorControl IsCustom = true; } + public bool IsUsingHDRPictureMode() + { + // Temporary workaround because I cannot read the picture mode at this time + return PictureSettings.Backlight == 100 && PictureSettings.Contrast == 100; + } + internal async Task SetBacklight(int backlight) { await _lgTvApi.SetSystemSettings("backlight", backlight.ToString()); @@ -572,21 +638,27 @@ namespace ColorControl internal async Task GetPictureSettings() { - var keys = new[] { "backlight", "brightness", "contrast", "color", "pictureMode", "colorGamut", "dynamicContrast", "peakBrightness", "smoothGradation", "energySaving", "motionProOLED" }; + //var keys = new[] { "backlight", "brightness", "contrast", "color", "pictureMode", "colorGamut", "dynamicContrast", "peakBrightness", "smoothGradation", "energySaving", "motionProOLED" }; + var keys = new[] { "backlight", "brightness", "contrast", "color" }; return await _lgTvApi.GetSystemSettings("picture", keys); } - private bool WakeAction(Dictionary parameters) + private bool WakeAction(Dictionary parameters) { return Wake(); } - private bool GenericPictureAction(Dictionary parameters) + private bool GenericPictureAction(Dictionary parameters) { - var settingName = parameters["name"]; - var value = parameters["value"]; - var category = parameters["category"]; + var settingName = parameters["name"].ToString(); + var stringValues = parameters["value"] as string[]; + var category = parameters["category"].ToString(); + object value = stringValues[0]; + if (stringValues.Length > 1) + { + value = stringValues.Select(s => int.Parse(s)).ToArray(); + } var task = _lgTvApi.SetSystemSettings(settingName, value, category); Utils.WaitForTask(task); diff --git a/ColorControl/LgPreset.cs b/ColorControl/LgPreset.cs index fc113c7..ffc7e41 100644 --- a/ColorControl/LgPreset.cs +++ b/ColorControl/LgPreset.cs @@ -20,7 +20,7 @@ namespace ColorControl [JsonIgnore] public int Y { get; set; } - public LgPreset() + public LgPreset() : base() { steps = new List(); } @@ -46,7 +46,7 @@ namespace ColorControl public static List GetColumnNames() { - return new List() { "Name", "Device|120", "App|200", "Steps|400", "Shortcut" }; + return new List() { "Name", "Device|120", "App|200", "Steps|400", "Shortcut", "Trigger" }; } public override List GetDisplayValues(Config config = null) @@ -90,6 +90,11 @@ namespace ColorControl values.Add(GetStepsDisplay()); values.Add(shortcut); + if (Triggers.Any()) + { + values.Add(Triggers.First().ToString()); + } + return values; } diff --git a/ColorControl/LgService.cs b/ColorControl/LgService.cs index 206fa6c..ea0936d 100644 --- a/ColorControl/LgService.cs +++ b/ColorControl/LgService.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -23,7 +22,20 @@ namespace ColorControl ShutDown = 4, StandBy = 5 } - + + internal class ProcessMonitorContext + { + public List Devices { get; set; } + public bool WasFullScreen { get; set; } + public Process[] StartedProcesses { get; set; } + public Process[] StoppedProcesses { get; set; } + public Process[] RunningProcesses { get; set; } + public bool IsNotificationDisabled { get; set; } + public Process ForegroundProcess { get; set; } + public bool ForegroundProcessIsFullScreen { get; set; } + public string LastFullScreenProcessName = string.Empty; + } + protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); public static string LgListAppsJson = "listApps.json"; @@ -52,7 +64,7 @@ namespace ColorControl private int _poweredOffByScreenSaverProcessId; private Task _monitorTask; private int _monitorTaskCounter; - + public LgService(string dataPath, bool allowPowerOn) : base(dataPath) { _allowPowerOn = allowPowerOn; @@ -352,7 +364,11 @@ namespace ColorControl return false; } - return await device.ExecutePreset(preset, reconnect); + var result = await device.ExecutePreset(preset, reconnect); + + _lastAppliedPreset = preset; + + return result; } public LgDevice GetPresetDevice(LgPreset preset) @@ -521,7 +537,7 @@ namespace ColorControl private void MonitorProcesses() { - var enableMonitoring = Devices.Any(d => d.PowerSwitchOnScreenSaver); + var enableMonitoring = ShouldMonitorProcesses(); if (enableMonitoring && _monitorTask == null) { _monitorTask = CheckProcesses(); @@ -532,22 +548,35 @@ namespace ColorControl } } + public bool ShouldMonitorProcesses() + { + return Devices.Any(d => d.PowerSwitchOnScreenSaver) || _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None)); + } + public async Task CheckProcesses() { var wasConnected = Devices.Any(d => d.PowerSwitchOnScreenSaver && d.IsConnected()); _monitorTaskCounter++; var validCounter = _monitorTaskCounter; var lastProcessId = 0; - //const int minBacklight = 30; - //const int maxBacklight = 50; - //var lastBacklight = minBacklight; - while (Devices.Any(d => d.PowerSwitchOnScreenSaver) && validCounter == _monitorTaskCounter) + var monitorContext = new ProcessMonitorContext(); + //Process[] lastProcesses = null; + + while (validCounter == _monitorTaskCounter) { - await Task.Delay(500); + await Task.Delay(_poweredOffByScreenSaver ? 500 : 1000); try { + var applicableDevices = Devices.Where(d => d.PowerSwitchOnScreenSaver || _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None) && + ((p.DeviceMacAddress == null && d == SelectedDevice) || p.DeviceMacAddress == d.MacAddress))); + + if (!applicableDevices.Any()) + { + break; + } + if (_poweredOffByScreenSaver && !UserSessionInfo.UserLocalSession) { continue; @@ -571,78 +600,112 @@ namespace ColorControl continue; } - var devices = Devices.Where(d => d.PowerSwitchOnScreenSaver && d.IsConnected()).ToList(); + var connectedDevices = applicableDevices.Where(d => d.IsConnected()).ToList(); - if (!devices.Any()) + if (!connectedDevices.Any()) { if (wasConnected) { - Logger.Debug("Screensaver check: TV(s) where connected, but not any longer"); + Logger.Debug("Process monitor: TV(s) where connected, but not any longer"); wasConnected = false; + continue; + } + else + { + var devices = applicableDevices.ToList(); + foreach (var device in devices) + { + Logger.Debug($"Process monitor: test connection with {device.Name}..."); + var test = await device.TestConnection(); + Logger.Debug("Process monitor: test connection result: " + test); + } + + connectedDevices = applicableDevices.Where(d => d.IsConnected()).ToList(); + + if (!connectedDevices.Any()) + { + continue; + } } - continue; } if (!wasConnected) { - Logger.Debug("Screensaver check: TV(s) where not connected, but connection has now been established"); + Logger.Debug("Process monitor: TV(s) where not connected, but connection has now been established"); wasConnected = true; } - //if (Utils.IsForegroundFullScreen()) + monitorContext.Devices = connectedDevices; + monitorContext.RunningProcesses = processes; + //if (lastProcesses != null) //{ - // if (lastBacklight != maxBacklight) - // { - // SelectedDevice?.SetBacklight(maxBacklight); - // lastBacklight = maxBacklight; - // } + // monitorContext.StartedProcesses = processes.Where(p => !lastProcesses.Any(lp => lp.Id == p.Id)).ToArray(); + // monitorContext.StoppedProcesses = lastProcesses.Where(lp => !processes.Any(p => p.Id == lp.Id)).ToArray(); //} - //else if (lastBacklight != minBacklight) + //else //{ - // SelectedDevice?.SetBacklight(minBacklight); - // lastBacklight = minBacklight; + // monitorContext.StartedProcesses = null; + // monitorContext.StoppedProcesses = null; //} + //if (monitorContext.StartedProcesses?.Any() ?? false) + //{ + // Logger.Debug("Started processes: " + string.Join(", ", monitorContext.StartedProcesses.Select(p => p.ProcessName))); + //} + //lastProcesses = processes; + var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr")); - if (process == null) + if (process != null) { - continue; - } + var parent = process.Parent(); - var parent = process.Parent(); - - if (parent?.ProcessName.Contains("winlogon") ?? false) - { - Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}"); - try + if (parent?.ProcessName.Contains("winlogon") ?? false) { - foreach (var device in devices) + Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}"); + try { - Logger.Debug($"Screensaver check: test connection with {device.Name}..."); - var test = await device.TestConnection(); - Logger.Debug("Screensaver check: test connection result: " + test); - if (!test) + foreach (var device in connectedDevices) { - continue; + Logger.Debug($"Screensaver check: test connection with {device.Name}..."); + var test = await device.TestConnection(); + Logger.Debug("Screensaver check: test connection result: " + test); + if (!test) + { + continue; + } + + Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver"); + try + { + _poweredOffByScreenSaver = await device.PowerOff(); + } + catch (Exception pex) + { + // Assume powering off was successful + _poweredOffByScreenSaver = true; + Logger.Error($"Screensaver check: exception while powering off (probably connection closed): " + pex.Message); + } + Logger.Debug($"Screensaver check: powered off tv {device.Name}"); } - Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver"); - _poweredOffByScreenSaver = await device.PowerOff(); + _poweredOffByScreenSaverProcessId = process.Id; + lastProcessId = 0; + } + catch (Exception e) + { + Logger.Error($"Screensaver check: can't power off: " + e.ToLogString()); } - - _poweredOffByScreenSaverProcessId = process.Id; - lastProcessId = 0; } - catch (Exception e) + else if (process.Id != lastProcessId) { - Logger.Error($"Screensaver check: can't power off: " + e.ToLogString()); + Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}"); + lastProcessId = process.Id; } } - else if (process.Id != lastProcessId) + else { - Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}"); - lastProcessId = process.Id; + await ExecuteProcessPresets(monitorContext); } } catch (Exception ex) @@ -652,6 +715,93 @@ namespace ColorControl } } + private async Task ExecuteProcessPresets(ProcessMonitorContext context) + { + var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch)); + if (!presets.Any()) + { + return; + } + + context.IsNotificationDisabled = Utils.IsNotificationDisabled(); + + var (processId, isFullScreen) = Utils.GetForegroundProcessIdAndIfFullScreen(); + + if (processId > 0) + { + context.ForegroundProcess = context.RunningProcesses.FirstOrDefault(p => p.Id == processId); + context.ForegroundProcessIsFullScreen = isFullScreen; + } + else + { + context.ForegroundProcess = null; + context.ForegroundProcessIsFullScreen = false; + } + + var changedProcesses = new List(); + if (context.ForegroundProcess != null) + { + changedProcesses.Add(context.ForegroundProcess); + } + + if (context.ForegroundProcess != null && context.ForegroundProcessIsFullScreen) + { + if (!context.LastFullScreenProcessName.Equals(context.ForegroundProcess.ProcessName)) + { + context.LastFullScreenProcessName = context.ForegroundProcess.ProcessName; + //Logger.Debug($"Foreground fullscreen app detected: {context.ForegroundProcess.ProcessName}"); + } + + presets = presets.Where(p => p.Triggers.Any(t => t.Conditions.HasFlag(PresetConditionType.FullScreen))); + context.WasFullScreen = true; + } + else if (context.WasFullScreen) + { + presets = presets.Where(p => p.Triggers.Any(t => !t.Conditions.HasFlag(PresetConditionType.FullScreen))); + context.WasFullScreen = false; + context.LastFullScreenProcessName = string.Empty; + } + + var isHDRActive = context.Devices.Any(d => d.IsUsingHDRPictureMode()); + + var triggerContext = new PresetTriggerContext + { + IsHDRActive = isHDRActive, + ForegroundProcess = context.ForegroundProcess, + ForegroundProcessIsFullScreen = context.ForegroundProcessIsFullScreen, + ChangedProcesses = changedProcesses, + IsNotificationDisabled = context.IsNotificationDisabled + }; + + var toApplyPresets = new List(); + foreach (var preset in presets) + { + if (preset.Triggers.Any(t => t.TriggerActive(triggerContext))) + { + toApplyPresets.Add(preset); + } + } + + if (toApplyPresets.Any()) + { + LgPreset toApplyPreset; + if (toApplyPresets.Count > 1) + { + toApplyPreset = toApplyPresets.FirstOrDefault(p => p.Triggers.Any(t => !t.IncludedProcesses.Contains("*"))); + toApplyPreset = toApplyPreset != null ? toApplyPreset : toApplyPresets.First(); + } + else + { + toApplyPreset = toApplyPresets.First(); + } + + if (_lastAppliedPreset != toApplyPreset) + { + await ApplyPreset(toApplyPreset); + } + } + } + internal void AddCustomDevice(LgDevice device) { Devices.Add(device); diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs index 797f63e..4ad9b1e 100644 --- a/ColorControl/MainForm.Designer.cs +++ b/ColorControl/MainForm.Designer.cs @@ -104,6 +104,13 @@ this.lvAmdPresets = new System.Windows.Forms.ListView(); this.tabLG = new System.Windows.Forms.TabPage(); this.scLgController = new System.Windows.Forms.SplitContainer(); + this.lblLgPresetExcludedProcesses = new System.Windows.Forms.Label(); + this.edtLgPresetExcludedProcesses = new System.Windows.Forms.TextBox(); + this.lblLgPresetIncludedProcesses = new System.Windows.Forms.Label(); + this.edtLgPresetIncludedProcesses = new System.Windows.Forms.TextBox(); + this.lblLgPresetTriggerCondition = new System.Windows.Forms.Label(); + this.cbxLgPresetTrigger = new System.Windows.Forms.ComboBox(); + this.lblLgPresetTrigger = new System.Windows.Forms.Label(); this.btnLgExpert = new System.Windows.Forms.Button(); this.mnuLgExpert = new System.Windows.Forms.ContextMenuStrip(this.components); this.mnuLgOLEDMotionPro = new System.Windows.Forms.ToolStripMenuItem(); @@ -138,6 +145,8 @@ this.mnuLgButtons = new System.Windows.Forms.ContextMenuStrip(this.components); this.mnuLgRcButtons = new System.Windows.Forms.ToolStripMenuItem(); this.mnuLgActions = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuLgNvPresets = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuLgAmdPresets = new System.Windows.Forms.ToolStripMenuItem(); this.btnCloneLg = new System.Windows.Forms.Button(); this.lblStepsLg = new System.Windows.Forms.Label(); this.btnLgDeviceFilterRefresh = new System.Windows.Forms.Button(); @@ -182,8 +191,8 @@ this.lbPlugins = new System.Windows.Forms.ListBox(); this.label7 = new System.Windows.Forms.Label(); this.lblInfo = new System.Windows.Forms.Label(); - this.mnuLgNvPresets = new System.Windows.Forms.ToolStripMenuItem(); - this.mnuLgAmdPresets = new System.Windows.Forms.ToolStripMenuItem(); + this.edtLgPresetTriggerConditions = new System.Windows.Forms.TextBox(); + this.btnLgPresetEditTriggerConditions = new System.Windows.Forms.Button(); this.tcMain.SuspendLayout(); this.tabNVIDIA.SuspendLayout(); this.mnuNvPresets.SuspendLayout(); @@ -941,6 +950,15 @@ // // scLgController.Panel1 // + this.scLgController.Panel1.Controls.Add(this.btnLgPresetEditTriggerConditions); + this.scLgController.Panel1.Controls.Add(this.edtLgPresetTriggerConditions); + this.scLgController.Panel1.Controls.Add(this.lblLgPresetExcludedProcesses); + this.scLgController.Panel1.Controls.Add(this.edtLgPresetExcludedProcesses); + this.scLgController.Panel1.Controls.Add(this.lblLgPresetIncludedProcesses); + this.scLgController.Panel1.Controls.Add(this.edtLgPresetIncludedProcesses); + this.scLgController.Panel1.Controls.Add(this.lblLgPresetTriggerCondition); + this.scLgController.Panel1.Controls.Add(this.cbxLgPresetTrigger); + this.scLgController.Panel1.Controls.Add(this.lblLgPresetTrigger); this.scLgController.Panel1.Controls.Add(this.btnLgExpert); this.scLgController.Panel1.Controls.Add(this.chkLgRemoteControlShow); this.scLgController.Panel1.Controls.Add(this.label3); @@ -977,6 +995,75 @@ this.scLgController.SplitterDistance = 767; this.scLgController.TabIndex = 43; // + // lblLgPresetExcludedProcesses + // + this.lblLgPresetExcludedProcesses.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblLgPresetExcludedProcesses.AutoSize = true; + this.lblLgPresetExcludedProcesses.Location = new System.Drawing.Point(407, 375); + this.lblLgPresetExcludedProcesses.Name = "lblLgPresetExcludedProcesses"; + this.lblLgPresetExcludedProcesses.Size = new System.Drawing.Size(105, 13); + this.lblLgPresetExcludedProcesses.TabIndex = 52; + this.lblLgPresetExcludedProcesses.Text = "Excluded processes:"; + // + // edtLgPresetExcludedProcesses + // + this.edtLgPresetExcludedProcesses.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.edtLgPresetExcludedProcesses.Enabled = false; + this.edtLgPresetExcludedProcesses.Location = new System.Drawing.Point(515, 373); + this.edtLgPresetExcludedProcesses.Name = "edtLgPresetExcludedProcesses"; + this.edtLgPresetExcludedProcesses.Size = new System.Drawing.Size(248, 20); + this.edtLgPresetExcludedProcesses.TabIndex = 51; + // + // lblLgPresetIncludedProcesses + // + this.lblLgPresetIncludedProcesses.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblLgPresetIncludedProcesses.AutoSize = true; + this.lblLgPresetIncludedProcesses.Location = new System.Drawing.Point(407, 349); + this.lblLgPresetIncludedProcesses.Name = "lblLgPresetIncludedProcesses"; + this.lblLgPresetIncludedProcesses.Size = new System.Drawing.Size(102, 13); + this.lblLgPresetIncludedProcesses.TabIndex = 50; + this.lblLgPresetIncludedProcesses.Text = "Included processes:"; + // + // edtLgPresetIncludedProcesses + // + this.edtLgPresetIncludedProcesses.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.edtLgPresetIncludedProcesses.Enabled = false; + this.edtLgPresetIncludedProcesses.Location = new System.Drawing.Point(515, 347); + this.edtLgPresetIncludedProcesses.Name = "edtLgPresetIncludedProcesses"; + this.edtLgPresetIncludedProcesses.Size = new System.Drawing.Size(248, 20); + this.edtLgPresetIncludedProcesses.TabIndex = 49; + // + // lblLgPresetTriggerCondition + // + this.lblLgPresetTriggerCondition.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblLgPresetTriggerCondition.AutoSize = true; + this.lblLgPresetTriggerCondition.Location = new System.Drawing.Point(407, 322); + this.lblLgPresetTriggerCondition.Name = "lblLgPresetTriggerCondition"; + this.lblLgPresetTriggerCondition.Size = new System.Drawing.Size(54, 13); + this.lblLgPresetTriggerCondition.TabIndex = 47; + this.lblLgPresetTriggerCondition.Text = "Condition:"; + // + // cbxLgPresetTrigger + // + this.cbxLgPresetTrigger.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cbxLgPresetTrigger.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbxLgPresetTrigger.Enabled = false; + this.cbxLgPresetTrigger.FormattingEnabled = true; + this.cbxLgPresetTrigger.Location = new System.Drawing.Point(515, 292); + this.cbxLgPresetTrigger.Name = "cbxLgPresetTrigger"; + this.cbxLgPresetTrigger.Size = new System.Drawing.Size(248, 21); + this.cbxLgPresetTrigger.TabIndex = 46; + // + // lblLgPresetTrigger + // + this.lblLgPresetTrigger.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblLgPresetTrigger.AutoSize = true; + this.lblLgPresetTrigger.Location = new System.Drawing.Point(407, 295); + this.lblLgPresetTrigger.Name = "lblLgPresetTrigger"; + this.lblLgPresetTrigger.Size = new System.Drawing.Size(43, 13); + this.lblLgPresetTrigger.TabIndex = 45; + this.lblLgPresetTrigger.Text = "Trigger:"; + // // btnLgExpert // this.btnLgExpert.ContextMenuStrip = this.mnuLgExpert; @@ -1306,21 +1393,33 @@ this.mnuLgNvPresets, this.mnuLgAmdPresets}); this.mnuLgButtons.Name = "mnuLgButtons"; - this.mnuLgButtons.Size = new System.Drawing.Size(181, 114); + this.mnuLgButtons.Size = new System.Drawing.Size(153, 92); this.mnuLgButtons.Opening += new System.ComponentModel.CancelEventHandler(this.mnuLgButtons_Opening); // // mnuLgRcButtons // this.mnuLgRcButtons.Name = "mnuLgRcButtons"; - this.mnuLgRcButtons.Size = new System.Drawing.Size(180, 22); + this.mnuLgRcButtons.Size = new System.Drawing.Size(152, 22); this.mnuLgRcButtons.Text = "Buttons"; // // mnuLgActions // this.mnuLgActions.Name = "mnuLgActions"; - this.mnuLgActions.Size = new System.Drawing.Size(180, 22); + this.mnuLgActions.Size = new System.Drawing.Size(152, 22); this.mnuLgActions.Text = "Actions"; // + // mnuLgNvPresets + // + this.mnuLgNvPresets.Name = "mnuLgNvPresets"; + this.mnuLgNvPresets.Size = new System.Drawing.Size(152, 22); + this.mnuLgNvPresets.Text = "NVIDIA presets"; + // + // mnuLgAmdPresets + // + this.mnuLgAmdPresets.Name = "mnuLgAmdPresets"; + this.mnuLgAmdPresets.Size = new System.Drawing.Size(152, 22); + this.mnuLgAmdPresets.Text = "AMD presets"; + // // btnCloneLg // this.btnCloneLg.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); @@ -1832,17 +1931,26 @@ this.lblInfo.TabIndex = 1; this.lblInfo.Text = "Info"; // - // mnuLgNvPresets + // edtLgPresetTriggerConditions // - this.mnuLgNvPresets.Name = "mnuLgNvPresets"; - this.mnuLgNvPresets.Size = new System.Drawing.Size(180, 22); - this.mnuLgNvPresets.Text = "NVIDIA presets"; + this.edtLgPresetTriggerConditions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.edtLgPresetTriggerConditions.Enabled = false; + this.edtLgPresetTriggerConditions.Location = new System.Drawing.Point(515, 319); + this.edtLgPresetTriggerConditions.Name = "edtLgPresetTriggerConditions"; + this.edtLgPresetTriggerConditions.ReadOnly = true; + this.edtLgPresetTriggerConditions.Size = new System.Drawing.Size(167, 20); + this.edtLgPresetTriggerConditions.TabIndex = 53; // - // mnuLgAmdPresets + // btnLgPresetEditTriggerConditions // - this.mnuLgAmdPresets.Name = "mnuLgAmdPresets"; - this.mnuLgAmdPresets.Size = new System.Drawing.Size(180, 22); - this.mnuLgAmdPresets.Text = "AMD presets"; + this.btnLgPresetEditTriggerConditions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.btnLgPresetEditTriggerConditions.Location = new System.Drawing.Point(689, 317); + this.btnLgPresetEditTriggerConditions.Name = "btnLgPresetEditTriggerConditions"; + this.btnLgPresetEditTriggerConditions.Size = new System.Drawing.Size(75, 23); + this.btnLgPresetEditTriggerConditions.TabIndex = 54; + this.btnLgPresetEditTriggerConditions.Text = "Edit..."; + this.btnLgPresetEditTriggerConditions.UseVisualStyleBackColor = true; + this.btnLgPresetEditTriggerConditions.Click += new System.EventHandler(this.btnLgPresetEditTriggerConditions_Click); // // MainForm // @@ -2055,6 +2163,15 @@ private System.Windows.Forms.ToolStripSeparator miLgExpertSeparator1; private System.Windows.Forms.ToolStripMenuItem mnuLgNvPresets; private System.Windows.Forms.ToolStripMenuItem mnuLgAmdPresets; + private System.Windows.Forms.ComboBox cbxLgPresetTrigger; + private System.Windows.Forms.Label lblLgPresetTrigger; + private System.Windows.Forms.Label lblLgPresetTriggerCondition; + private System.Windows.Forms.Label lblLgPresetExcludedProcesses; + private System.Windows.Forms.TextBox edtLgPresetExcludedProcesses; + private System.Windows.Forms.Label lblLgPresetIncludedProcesses; + private System.Windows.Forms.TextBox edtLgPresetIncludedProcesses; + private System.Windows.Forms.Button btnLgPresetEditTriggerConditions; + private System.Windows.Forms.TextBox edtLgPresetTriggerConditions; } } diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index b9c9f36..5227e04 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -1092,6 +1092,11 @@ namespace ColorControl btnLgAddButton.Enabled = enabled; cbxLgApps.Enabled = enabled; btnDeleteLg.Enabled = enabled; + cbxLgPresetTrigger.Enabled = enabled; + edtLgPresetTriggerConditions.Enabled = enabled; + btnLgPresetEditTriggerConditions.Enabled = enabled; + edtLgPresetIncludedProcesses.Enabled = enabled; + edtLgPresetExcludedProcesses.Enabled = enabled; var preset = GetSelectedLgPreset(); @@ -1116,6 +1121,16 @@ namespace ColorControl } cbxLgPresetDevice.SelectedIndex = index; + + var trigger = preset.Triggers.FirstOrDefault(); + + Utils.SetComboBoxEnumIndex(cbxLgPresetTrigger, (int)(trigger?.Trigger ?? PresetTriggerType.None)); + + edtLgPresetTriggerConditions.Text = Utils.GetDescriptions(trigger != null ? (int)trigger.Conditions : 0).Join(", "); + edtLgPresetTriggerConditions.Tag = trigger != null ? (int)trigger.Conditions : 0; + + edtLgPresetIncludedProcesses.Text = trigger?.IncludedProcesses?.Join(", ") ?? string.Empty; + edtLgPresetExcludedProcesses.Text = trigger?.ExcludedProcesses?.Join(", ") ?? string.Empty; } else { @@ -1124,6 +1139,11 @@ namespace ColorControl edtShortcutLg.Text = string.Empty; edtStepsLg.Text = string.Empty; cbxLgPresetDevice.SelectedIndex = -1; + cbxLgPresetTrigger.SelectedIndex = -1; + edtLgPresetTriggerConditions.Text = string.Empty; + edtLgPresetTriggerConditions.Tag = 0; + edtLgPresetIncludedProcesses.Text = string.Empty; + edtLgPresetExcludedProcesses.Text = string.Empty; } } @@ -1177,6 +1197,17 @@ namespace ColorControl preset.appId = lgApp.appId; } + var triggerType = Utils.GetComboBoxEnumItem(cbxLgPresetTrigger); + preset.UpdateTrigger(triggerType, + (PresetConditionType)edtLgPresetTriggerConditions.Tag, + edtLgPresetIncludedProcesses.Text, + edtLgPresetExcludedProcesses.Text); + + if (triggerType != PresetTriggerType.None) + { + _lgService.InstallEventHandlers(); + } + var shortcutChanged = !shortcut.Equals(preset.shortcut); if (shortcutChanged) { @@ -1184,27 +1215,8 @@ namespace ColorControl } var text = edtStepsLg.Text; - var clearSteps = string.IsNullOrEmpty(text); - preset.steps.Clear(); - if (!clearSteps) - { - //text = text.Replace(" ", string.Empty); - var steps = text.Split(','); - for (var i = 0; i < steps.Length; i++) - { - var step = steps[i]; - if (step.IndexOf("(") > -1) - { - steps[i] = step.Trim(); - } - else - { - steps[i] = step.Replace(" ", string.Empty); - } - } - preset.steps.AddRange(steps); - } + Utils.ParseWords(preset.steps, text); AddOrUpdateItemLg(); @@ -1262,27 +1274,7 @@ namespace ColorControl { if (tcMain.SelectedTab == tabLG) { - if (cbxLgDevices.Items.Count == 0 && _lgService != null) - { - var devices = _lgService.Devices; - if (devices == null || !devices.Any()) - { - RefreshLgDevices(); - } - else - { - FillLgDevices(); - } - - if (scLgController.Panel2.Controls.Count == 0) - { - var rcPanel = new RemoteControlPanel(_lgService, _lgService.GetRemoteControlButtons()); - rcPanel.Parent = scLgController.Panel2; - rcPanel.Dock = DockStyle.Fill; - } - chkLgRemoteControlShow.Checked = _lgService.Config.ShowRemoteControl; - scLgController.Panel2Collapsed = !_lgService.Config.ShowRemoteControl; - } + InitLgTab(); } else if (tcMain.SelectedTab == tabLog) { @@ -1294,25 +1286,57 @@ namespace ColorControl } else if (tcMain.SelectedTab == tabOptions) { - grpNvidiaOptions.Visible = _nvService != null; - if (grpNvidiaOptions.Visible) + InitOptionsTab(); + } + } + + private void InitLgTab() + { + if (cbxLgDevices.Items.Count == 0 && _lgService != null) + { + var devices = _lgService.Devices; + if (devices == null || !devices.Any()) { - var firstTime = cbxDitheringBitDepth.Items.Count == 0; + RefreshLgDevices(); + } + else + { + FillLgDevices(); + } - if (firstTime) - { - cbxDitheringBitDepth.Items.AddRange(Utils.GetDescriptions().ToArray()); - cbxDitheringMode.Items.AddRange(Utils.GetDescriptions().ToArray()); - } + if (scLgController.Panel2.Controls.Count == 0) + { + var rcPanel = new RemoteControlPanel(_lgService, _lgService.GetRemoteControlButtons()); + rcPanel.Parent = scLgController.Panel2; + rcPanel.Dock = DockStyle.Fill; + } + chkLgRemoteControlShow.Checked = _lgService.Config.ShowRemoteControl; + scLgController.Panel2Collapsed = !_lgService.Config.ShowRemoteControl; - var preset = _nvService.GetLastAppliedPreset() ?? GetSelectedNvPreset(); - if (firstTime || preset != null) - { - chkDitheringEnabled.Checked = preset?.ditheringEnabled ?? true; - cbxDitheringBitDepth.SelectedIndex = (int)(preset?.ditheringBits ?? (int)NvDitherBits.Bits8); - cbxDitheringMode.SelectedIndex = (int)(preset?.ditheringMode ?? (int)NvDitherMode.Temporal); - FillGradient(); - } + Utils.BuildComboBox(cbxLgPresetTrigger, PresetTriggerType.Resume, PresetTriggerType.Screensaver, PresetTriggerType.Shutdown, PresetTriggerType.Standby, PresetTriggerType.Startup); + } + } + + private void InitOptionsTab() + { + grpNvidiaOptions.Visible = _nvService != null; + if (grpNvidiaOptions.Visible) + { + var firstTime = cbxDitheringBitDepth.Items.Count == 0; + + if (firstTime) + { + cbxDitheringBitDepth.Items.AddRange(Utils.GetDescriptions().ToArray()); + cbxDitheringMode.Items.AddRange(Utils.GetDescriptions().ToArray()); + } + + var preset = _nvService.GetLastAppliedPreset() ?? GetSelectedNvPreset(); + if (firstTime || preset != null) + { + chkDitheringEnabled.Checked = preset?.ditheringEnabled ?? true; + cbxDitheringBitDepth.SelectedIndex = (int)(preset?.ditheringBits ?? (int)NvDitherBits.Bits8); + cbxDitheringMode.SelectedIndex = (int)(preset?.ditheringMode ?? (int)NvDitherMode.Temporal); + FillGradient(); } } } @@ -1630,7 +1654,7 @@ namespace ColorControl { lines = File.ReadAllLines(filename); } - var reversedLines = lines.Reverse().ToList(); + var reversedLines = lines.ToList(); var builder = new StringBuilder(); reversedLines.ForEach(line => builder.AppendLine(line)); edtLog.Text = builder.ToString(); @@ -1729,7 +1753,7 @@ namespace ColorControl return; } - value = values.First(); + value = values.First().Value.ToString(); } } else @@ -1754,7 +1778,7 @@ namespace ColorControl return; } - value = values.First(); + value = values.First().Value.ToString(); } if (!string.IsNullOrEmpty(value)) @@ -2001,7 +2025,7 @@ Do you want to continue?"; var preset = GetSelectedNvPreset(); - preset.ditheringBits = uint.Parse(item.Tag.ToString()); + preset.ditheringMode = uint.Parse(item.Tag.ToString()); AddOrUpdateItem(); @@ -2468,7 +2492,7 @@ Do you want to continue?"; var values = MessageForms.ShowDialog("Add tv", new[] { "Name", "Ip-address", "MAC-address" }, ValidateAddDevice); if (values.Any()) { - var device = new LgDevice(values[0], values[1], values[2]); + var device = new LgDevice(values[0].Value.ToString(), values[1].Value.ToString(), values[2].Value.ToString()); var form = MessageForms.ShowProgress("Connecting to device..."); @@ -2497,9 +2521,9 @@ Do you want to continue?"; } } - private string ValidateAddDevice(List values) + private string ValidateAddDevice(IEnumerable values) { - if (values.Any(v => string.IsNullOrEmpty(v))) + if (values.Any(v => string.IsNullOrEmpty(v.Value?.ToString()))) { return "Please fill in all the fields"; } @@ -2538,7 +2562,7 @@ Do you want to continue?"; var device = _lgService.GetPresetDevice(preset); var actions = device?.GetInvokableActions(); - foreach (var action in actions.Where(a => a.Category == null || !a.Category.Equals("other"))) + foreach (var action in actions) { var text = action.Name; @@ -2694,5 +2718,36 @@ Do you want to continue?" cbxLgApps.SelectedIndex = -1; } } + + private void btnLgPresetEditTriggerConditions_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); + + if (preset == null) + { + return; + } + + var dropDownValues = Utils.GetDescriptions(fromValue: 1); + + var values = MessageForms.ShowDialog("Set trigger conditions", new[] { + new MessageForms.FieldDefinition + { + Label = "Set desired trigger conditions", + FieldType = MessageForms.FieldType.Flags, + Values = dropDownValues, + Value = edtLgPresetTriggerConditions.Tag ?? 0 + } + }); + + if (!values.Any()) + { + return; + } + + var value = (PresetConditionType)values.First().Value; + edtLgPresetTriggerConditions.Tag = (PresetConditionType)values.First().Value; + edtLgPresetTriggerConditions.Text = Utils.GetDescriptions((int)value).Join(", "); + } } } \ No newline at end of file diff --git a/ColorControl/MessageForms.cs b/ColorControl/MessageForms.cs index 4da01fd..95d78eb 100644 --- a/ColorControl/MessageForms.cs +++ b/ColorControl/MessageForms.cs @@ -12,7 +12,8 @@ namespace ColorControl { Text, Numeric, - DropDown + DropDown, + Flags } public class FieldDefinition @@ -22,6 +23,7 @@ namespace ColorControl public IEnumerable Values { get; set; } public decimal MinValue { get; set; } public decimal MaxValue { get; set; } + public object Value { get; set; } } public static Form MainForm; @@ -46,19 +48,19 @@ namespace ColorControl return MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Information); } - public static List ShowDialog(string caption, IEnumerable labels, Func, string> validateFunc = null) + public static List ShowDialog(string caption, IEnumerable labels, Func, string> validateFunc = null) { var fieldDefinitions = labels.Select(l => new FieldDefinition { Label = l - }); + }).ToList(); return ShowDialog(caption, fieldDefinitions, validateFunc); } - public static List ShowDialog(string caption, IEnumerable fields, Func, string> validateFunc = null) + public static List ShowDialog(string caption, IEnumerable fields, Func, string> validateFunc = null) { - var values = new List(); + var values = new List(); var prompt = new Form() { @@ -131,6 +133,35 @@ namespace ColorControl control = comboBox; break; + case FieldType.Flags: + var checkedListBox = new CheckedListBox + { + Left = 40, + Top = top, + Width = 200, + Height = 100 + }; + + top += 80; + prompt.Height += 80; + + if (field.Values != null && field.Values.Any()) + { + var compoundValue = (int)field.Value; + var enumValue = 1; + foreach (var value in field.Values) + { + var isChecked = (compoundValue & enumValue) == enumValue; + + checkedListBox.Items.Add(value, isChecked); + + enumValue *= 2; + } + } + + control = checkedListBox; + break; + default: continue; } @@ -138,24 +169,51 @@ namespace ColorControl top += 30; prompt.Controls.Add(control); boxes.Add(control); + control.Tag = field; } var confirmation = new Button() { Text = "OK", Left = 365, Width = 75, Top = prompt.ClientRectangle.Height - 30 }; confirmation.Click += (sender, e) => { - values.AddRange(boxes.Select(b => b.Text.Trim())); + foreach (var box in boxes) + { + var field = (FieldDefinition)box.Tag; + + switch (field.FieldType) + { + case FieldType.Flags: + var checkedListBox = (CheckedListBox)box; + + var compoundValue = 0; + var enumValue = 1; + for (var i = 0; i < checkedListBox.Items.Count; i++) + { + compoundValue += checkedListBox.GetItemChecked(i) ? enumValue : 0; + enumValue *= 2; + } + + field.Value = compoundValue; + break; + default: + field.Value = box.Text.Trim(); + break; + } + } + if (validateFunc != null) { - var message = validateFunc(values); + var message = validateFunc(fields); if (!string.IsNullOrEmpty(message)) { WarningOk(message); - values.Clear(); return; } } + + values.AddRange(fields); + prompt.Close(); }; prompt.Controls.Add(confirmation); diff --git a/ColorControl/NvPreset.cs b/ColorControl/NvPreset.cs index 3a3dcbf..9685794 100644 --- a/ColorControl/NvPreset.cs +++ b/ColorControl/NvPreset.cs @@ -22,7 +22,7 @@ namespace ColorControl public uint ditheringBits { get; set; } public uint ditheringMode { get; set; } - public NvPreset() + public NvPreset() : base() { applyColorData = true; applyDithering = false; diff --git a/ColorControl/NvService.cs b/ColorControl/NvService.cs index 6667541..c5925f4 100644 --- a/ColorControl/NvService.cs +++ b/ColorControl/NvService.cs @@ -290,26 +290,30 @@ namespace ColorControl { if (enabled != IsHDREnabled()) { - ToggleHDR(); + CCD.SetHDRState(enabled, display.Name); + //ToggleHDR(); } return; } - var newMaster = new MasteringDisplayColorData(); - var hdr = new HDRColorData(enabled ? ColorDataHDRMode.UHDA : ColorDataHDRMode.Off, newMaster, colorData?.ColorFormat, colorData?.DynamicRange, colorData?.ColorDepth); + CCD.SetHDRState(enabled, display.Name); - display.DisplayDevice.SetHDRColorData(hdr); + //var newMaster = new MasteringDisplayColorData(); + //var hdr = new HDRColorData(enabled ? ColorDataHDRMode.UHDA : ColorDataHDRMode.Off, newMaster, colorData?.ColorFormat, colorData?.DynamicRange, colorData?.ColorDepth); + + //display.DisplayDevice.SetHDRColorData(hdr); // HDR will not always be disabled this way, then we can only disable it through the display settings - if (!enabled && IsHDREnabled()) - { - ToggleHDR(); - } - else if (enabled) - { - // Currectly there seems to be a bug that after enabling HDR via NVAPI, some settings are only applied upon opening the Display Settings... - ////OpenDisplaySettings(); - } + //if (!enabled && IsHDREnabled()) + //{ + // CCD.SetHDRState(enabled); + // //ToggleHDR(); + //} + //else if (enabled) + //{ + // // Currectly there seems to be a bug that after enabling HDR via NVAPI, some settings are only applied upon opening the Display Settings... + // ////OpenDisplaySettings(); + //} } public bool SetDithering(bool enabled, uint bits = 1, uint mode = 4, NvPreset preset = null) diff --git a/ColorControl/PresetBase.cs b/ColorControl/PresetBase.cs index cd9fc4f..8ed825e 100644 --- a/ColorControl/PresetBase.cs +++ b/ColorControl/PresetBase.cs @@ -1,36 +1,123 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; namespace ColorControl { - [Flags] - enum PresetTrigger + enum PresetTriggerType { + [Description("None")] + None = 0, [Description("Startup")] Startup = 1, [Description("Resume")] Resume = 2, [Description("Shutdown")] - Shutdown = 4, + Shutdown = 3, [Description("Standby")] - Standby = 8, + Standby = 4, [Description("Screensaver")] - Screensaver = 16, - [Description("Process start")] - ProcessStart = 32, - [Description("Process exit")] - ProcessExit = 64 + Screensaver = 5, + [Description("Process switch")] + ProcessSwitch = 6, + } + + [Flags] + enum PresetConditionType + { + [Description("None")] + None = 0, + [Description("SDR")] + SDR = 1, + [Description("HDR")] + HDR = 2, + [Description("Full Screen")] + FullScreen = 4, + [Description("Notifications Disabled")] + NotificationsDisabled = 8, + } + + class PresetTrigger + { + public PresetTriggerType Trigger { get; set; } + public List IncludedProcesses { get; set; } + public List ExcludedProcesses { get; set; } + public PresetConditionType Conditions { get; set; } + + public PresetTrigger() + { + IncludedProcesses = new List(); + ExcludedProcesses = new List(); + } + + public bool TriggerActive(PresetTriggerContext context) + { + var active = Conditions == PresetConditionType.None || (Conditions.HasFlag(PresetConditionType.SDR) && !context.IsHDRActive) || (Conditions.HasFlag(PresetConditionType.HDR) && context.IsHDRActive); + var allProcesses = IncludedProcesses.Contains("*"); + + if (Trigger == PresetTriggerType.ProcessSwitch) + { + active = active && (allProcesses || (context.ChangedProcesses?.Any() ?? false)); + + if (active) + { + var included = allProcesses || context.ChangedProcesses.Any(cp => IncludedProcesses.Any(ip => cp.ProcessName.Equals(ip, StringComparison.OrdinalIgnoreCase))); + var excluded = context.ChangedProcesses.Any(cp => ExcludedProcesses.Any(ep => cp.ProcessName.Equals(ep, StringComparison.OrdinalIgnoreCase))); + + var screenSizeCheck = (!Conditions.HasFlag(PresetConditionType.FullScreen) && !context.ForegroundProcessIsFullScreen) || + (context.ForegroundProcess != null && context.ForegroundProcessIsFullScreen && + (allProcesses || IncludedProcesses.Any(ip => context.ForegroundProcess.ProcessName.Equals(ip, StringComparison.OrdinalIgnoreCase)))); + + var notificationsDisabledCheck = !Conditions.HasFlag(PresetConditionType.NotificationsDisabled) || context.IsNotificationDisabled; + + active = included && !excluded && screenSizeCheck && notificationsDisabledCheck; + } + } + + return active; + } + + public override string ToString() + { + if (Trigger == PresetTriggerType.None) + { + 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)}"; + } + + public static string DisplayProcesses(IEnumerable processes) + { + return processes.Contains("*") ? "all processes" : string.Join(", ", processes); + } + } + + class PresetTriggerContext + { + public bool IsHDRActive { get; set; } + public Process ForegroundProcess { get; set; } + public bool ForegroundProcessIsFullScreen { get; set; } + public List ChangedProcesses { get; set; } + public bool IsNotificationDisabled { get; set; } } internal abstract class PresetBase { public string name { get; set; } public string shortcut { get; set; } - public PresetTrigger Triggers { get; set; } + public List Triggers { get; set; } private int _id; + protected PresetBase() + { + Triggers = new List(); + } + public int id { get @@ -68,5 +155,39 @@ namespace ColorControl } public abstract List GetDisplayValues(Config config = null); + + public void UpdateTrigger(PresetTriggerType triggerType, PresetConditionType conditions, string includedProcesses, string excludedProcesses) + { + if (!Triggers.Any()) + { + Triggers.Add(new PresetTrigger()); + } + + var trigger = Triggers.First(); + + trigger.Trigger = triggerType; + trigger.Conditions = conditions; + + if (trigger.IncludedProcesses == null) + { + trigger.IncludedProcesses = new List(); + } + if (trigger.ExcludedProcesses == null) + { + trigger.ExcludedProcesses = new List(); + } + Utils.ParseWords(trigger.IncludedProcesses, includedProcesses); + Utils.ParseWords(trigger.ExcludedProcesses, excludedProcesses); + } + + public PresetTrigger AddTrigger() + { + if (!Triggers.Any()) + { + Triggers.Add(new PresetTrigger()); + } + + return Triggers.First(); + } } } \ No newline at end of file diff --git a/ColorControl/Properties/AssemblyInfo.cs b/ColorControl/Properties/AssemblyInfo.cs index f4e9166..683c75c 100644 --- a/ColorControl/Properties/AssemblyInfo.cs +++ b/ColorControl/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.3.0.0")] -[assembly: AssemblyFileVersion("3.3.0.0")] +[assembly: AssemblyVersion("4.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] diff --git a/ColorControl/Utils.cs b/ColorControl/Utils.cs index 527fc4e..fdee71b 100644 --- a/ColorControl/Utils.cs +++ b/ColorControl/Utils.cs @@ -36,6 +36,23 @@ namespace ColorControl Win = 8 } + public enum UserNotificationState : int + { + QUNS_NOT_PRESENT = 1, + QUNS_BUSY, + QUNS_RUNNING_D3D_FULL_SCREEN, + QUNS_PRESENTATION_MODE, + QUNS_ACCEPTS_NOTIFICATIONS, + QUNS_QUIET_TIME, + QUNS_APP + } + + public static UserNotificationState[] NotificationsDisabledStates = new[] { + UserNotificationState.QUNS_BUSY, + UserNotificationState.QUNS_RUNNING_D3D_FULL_SCREEN, + UserNotificationState.QUNS_PRESENTATION_MODE, + }; + public const int WM_BRINGTOFRONT = NativeConstants.WM_USER + 1; public const int ENUM_CURRENT_SETTINGS = -1; @@ -75,6 +92,12 @@ namespace ColorControl [return: MarshalAs(UnmanagedType.Bool)] public static extern bool FreeConsole(); + [DllImport("shell32.dll")] + public static extern int SHQueryUserNotificationState(out UserNotificationState pquns); + + [DllImport("user32.dll", SetLastError = true)] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + //[DllImport("Wtsapi32.dll")] //public extern static bool WTSRegisterSessionNotification(IntPtr hWnd, uint dwFlags); @@ -83,6 +106,7 @@ namespace ColorControl private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); private static bool WinKeyDown = false; + private static UserNotificationState LastNotificationState; public static Bitmap SubPixelShift(Bitmap bitmap) { @@ -428,11 +452,15 @@ namespace ColorControl return null; // could also return string.Empty } - public static List GetDescriptions() where T : IConvertible + public static List GetDescriptions(int value = -1, int fromValue = 0) where T : IConvertible { var list = new List(); foreach (var enumValue in Enum.GetValues(typeof(T))) { + if ((int)enumValue < fromValue || value >= 0 && ((int)enumValue & value) == 0) + { + continue; + } list.Add(((T)enumValue).GetDescription()); } return list; @@ -579,6 +607,58 @@ namespace ColorControl } } + public static void BuildComboBox(ComboBox comboBox, params T[] skip) where T : IConvertible + { + if (comboBox.Items.Count == 0) + { + foreach (var enumValue in Enum.GetValues(typeof(T))) + { + if (skip.Contains((T)enumValue)) + { + continue; + } + var item = new EnumComboBoxItem + { + Name = ((T)enumValue).GetDescription(), + Id = (int)enumValue + }; + + comboBox.Items.Add(item); + } + } + } + + public static int SetComboBoxEnumIndex(ComboBox comboBox, int value) + { + var index = 0; + + for (var i = 0; i < comboBox.Items.Count; i++) + { + var item = comboBox.Items[i] as EnumComboBoxItem; + if (item.Id == value) + { + index = i; + break; + } + } + + comboBox.SelectedIndex = index; + + return index; + } + + public static T GetComboBoxEnumItem(ComboBox comboBox) where T : IConvertible + { + if (comboBox.SelectedIndex == -1) + { + return (T)Enum.Parse(typeof(T), "None"); + } + + var item = comboBox.SelectedItem as EnumComboBoxItem; + + return (T)Enum.Parse(typeof(T), item.Id.ToString()); + } + public static string FormatKeyboardShortcut(KeyEventArgs keyEvent) { var pressedModifiers = keyEvent.Modifiers; @@ -674,7 +754,14 @@ namespace ColorControl item.Text = values[0]; for (var i = 1; i < values.Count; i++) { - item.SubItems[i].Text = values[i]; + if (item.SubItems.Count == i) + { + item.SubItems.Add(values[i]); + } + else + { + item.SubItems[i].Text = values[i]; + } } } } @@ -743,18 +830,34 @@ namespace ColorControl } } + public static bool IsForegroundFullScreenAndDisabledNotifications(Screen screen = null) + { + return IsNotificationDisabled() && IsForegroundFullScreen(screen); + } + + public static bool IsNotificationDisabled() + { + var result = SHQueryUserNotificationState(out var state); + + if (result == NativeConstants.ERROR_SUCCESS && state != LastNotificationState) + { + Logger.Debug($"Detected NotificationState change from {LastNotificationState} to {state}"); + LastNotificationState = state; + } + + return NotificationsDisabledStates.Contains(state); + //return state != UserNotificationState.QUNS_ACCEPTS_NOTIFICATIONS && state != UserNotificationState.QUNS_QUIET_TIME; + } + public static bool IsForegroundFullScreen(Screen screen = null) { - if (screen == null) { screen = Screen.PrimaryScreen; } - tagRECT rect = new tagRECT(); - IntPtr hWnd = (IntPtr)NativeMethods.GetForegroundWindow(); + var hWnd = NativeMethods.GetForegroundWindow(); - - NativeMethods.GetWindowRect(hWnd, out rect); + NativeMethods.GetWindowRect(hWnd, out var rect); /* in case you want the process name: uint procId = 0; @@ -763,19 +866,76 @@ namespace ColorControl Console.WriteLine(proc.ProcessName); */ + return screen.Bounds.Width <= (rect.right - rect.left) && screen.Bounds.Height <= (rect.bottom - rect.top); + } - if (screen.Bounds.Width <= (rect.right - rect.left) && screen.Bounds.Height <= (rect.bottom - rect.top)) + public static (uint, bool) GetForegroundProcessIdAndIfFullScreen(Screen screen = null) + { + if (screen == null) { - Console.WriteLine("Fullscreen!"); - return true; + screen = Screen.PrimaryScreen; } - else + var hWnd = NativeMethods.GetForegroundWindow(); + + if (hWnd == IntPtr.Zero) { - Console.WriteLine("Nope, :-("); - return false; + return (0, false); } + NativeMethods.GetWindowRect(hWnd, out var rect); + GetWindowThreadProcessId(hWnd, out var processId); + + var isFullScreen = screen.Bounds.Width <= (rect.right - rect.left) && screen.Bounds.Height <= (rect.bottom - rect.top); + + return (processId, isFullScreen); + } + + public static Process GetForegroundFullScreenProcess(Screen screen = null) + { + var (processId, isFullScreen) = GetForegroundProcessIdAndIfFullScreen(screen); + + if (!isFullScreen) + { + return null; + } + + return Process.GetProcessById((int)processId); + } + + public static void ParseWords(List target, string text) + { + target.Clear(); + + if (string.IsNullOrEmpty(text)) + { + return; + } + var words = text.Split(','); + for (var i = 0; i < words.Length; i++) + { + var word = words[i]; + if (word.IndexOf("(") > -1) + { + words[i] = word.Trim(); + } + else + { + words[i] = word.Replace(" ", string.Empty); + } + } + target.AddRange(words); + } + } + + public class EnumComboBoxItem + { + public string Name; + public int Id; + + public override string ToString() + { + return Name; } } diff --git a/ColorControl/lgtv/ClientKeyStore.cs b/ColorControl/lgtv/ClientKeyStore.cs index 954240d..c1cf643 100644 --- a/ColorControl/lgtv/ClientKeyStore.cs +++ b/ColorControl/lgtv/ClientKeyStore.cs @@ -6,10 +6,12 @@ namespace LgTv public class ClientKeyStore { private string ip; + public ClientKeyStore(string ip) { this.ip = ip; } + public string GetClientKey() { var filename = Path.Combine(Utils.GetDataPath(), ip + "_ClientKey.txt"); @@ -19,10 +21,34 @@ namespace LgTv } return null; } + public void SaveClientKey(string key) { var filename = Path.Combine(Utils.GetDataPath(), ip + "_ClientKey.txt"); File.WriteAllText(filename, key); } + + public bool HasValidHandShake(string expectedHandShake) + { + var handShake = GetHandShake(); + + return handShake?.Equals(expectedHandShake) ?? false; + } + + public string GetHandShake() + { + var filename = Path.Combine(Utils.GetDataPath(), ip + "_HandShake.txt"); + if (File.Exists(filename)) + { + return File.ReadAllText(filename); + } + return null; + } + + public void SaveHandShake(string data) + { + var filename = Path.Combine(Utils.GetDataPath(), ip + "_HandShake.txt"); + File.WriteAllText(filename, data); + } } } diff --git a/ColorControl/lgtv/LgTvApi.cs b/ColorControl/lgtv/LgTvApi.cs index 18b820b..0d5aed6 100644 --- a/ColorControl/lgtv/LgTvApi.cs +++ b/ColorControl/lgtv/LgTvApi.cs @@ -34,15 +34,31 @@ namespace LgTv high } + public enum LowToAuto + { + low, + medium, + high, + auto + } + public enum OffToOn { off, on } + public enum OffToAuto2 + { + off, + on, + auto + } + public enum OffToAuto { off, + on, low, medium, high, @@ -67,6 +83,13 @@ namespace LgTv screen_off } + public enum DynamicTonemapping + { + on, + off, + HGIG, + } + public enum TruMotionMode { off, @@ -86,15 +109,9 @@ namespace LgTv private List _appList; private string _ip; - private const string BEFORE_PAIR_HAND_SHAKE = "{\"type\":\"register\",\"id\":\"register_0\",\"payload\":{\"forcePairing\":false,\"pairingType\":\"PROMPT\",\"manifest\":{\"manifestVersion\":1,\"appVersion\":\"1.1\",\"signed\":{\"created\":\"20140509\",\"appId\":\"com.lge.test\",\"vendorId\":\"com.lge\",\"localizedAppNames\":{\"\":\"LG Remote App\",\"ko-KR\":\"리모컨 앱\",\"zxx-XX\":\"ЛГ Rэмotэ AПП\"},\"localizedVendorNames\":{\"\":\"LG Electronics\"},\"permissions\":[\"TEST_SECURE\",\"CONTROL_INPUT_TEXT\",\"CONTROL_MOUSE_AND_KEYBOARD\",\"READ_INSTALLED_APPS\",\"READ_LGE_SDX\",\"READ_NOTIFICATIONS\",\"SEARCH\",\"WRITE_SETTINGS\",\"WRITE_NOTIFICATION_ALERT\",\"CONTROL_POWER\",\"READ_CURRENT_CHANNEL\",\"READ_RUNNING_APPS\",\"READ_UPDATE_INFO\",\"UPDATE_FROM_REMOTE_APP\",\"READ_LGE_TV_INPUT_EVENTS\",\"READ_TV_CURRENT_TIME\"],\"serial\":\"2f930e2d2cfe083771f68e4fe7bb07\"},\"permissions\":[\"LAUNCH\",\"LAUNCH_WEBAPP\",\"APP_TO_APP\",\"CLOSE\",\"TEST_OPEN\",\"TEST_PROTECTED\",\"CONTROL_AUDIO\",\"CONTROL_DISPLAY\",\"CONTROL_INPUT_JOYSTICK\",\"CONTROL_INPUT_MEDIA_RECORDING\",\"CONTROL_INPUT_MEDIA_PLAYBACK\",\"CONTROL_INPUT_TV\",\"CONTROL_POWER\",\"READ_APP_STATUS\",\"READ_CURRENT_CHANNEL\",\"READ_INPUT_DEVICE_LIST\",\"READ_NETWORK_STATE\",\"READ_RUNNING_APPS\",\"READ_TV_CHANNEL_LIST\",\"WRITE_NOTIFICATION_TOAST\",\"READ_POWER_STATE\",\"READ_COUNTRY_INFO\"],\"signatures\":[{\"signatureVersion\":1,\"signature\":\"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==\"}]}}}"; + private const string BEFORE_PAIR_HAND_SHAKE = "{\"type\":\"register\",\"id\":\"register_0\",\"payload\":{\"forcePairing\":false,\"pairingType\":\"PROMPT\",\"manifest\":{\"manifestVersion\":1,\"appVersion\":\"1.1\",\"signed\":{\"created\":\"20140509\",\"appId\":\"com.lge.test\",\"vendorId\":\"com.lge\",\"localizedAppNames\":{\"\":\"LG Remote App\",\"ko-KR\":\"리모컨 앱\",\"zxx-XX\":\"ЛГ Rэмotэ AПП\"},\"localizedVendorNames\":{\"\":\"LG Electronics\"},\"permissions\":[\"TEST_SECURE\",\"CONTROL_INPUT_TEXT\",\"CONTROL_MOUSE_AND_KEYBOARD\",\"READ_INSTALLED_APPS\",\"READ_LGE_SDX\",\"READ_NOTIFICATIONS\",\"SEARCH\",\"WRITE_SETTINGS\",\"WRITE_NOTIFICATION_ALERT\",\"CONTROL_POWER\",\"READ_CURRENT_CHANNEL\",\"READ_RUNNING_APPS\",\"READ_UPDATE_INFO\",\"UPDATE_FROM_REMOTE_APP\",\"READ_LGE_TV_INPUT_EVENTS\",\"READ_TV_CURRENT_TIME\"],\"serial\":\"2f930e2d2cfe083771f68e4fe7bb07\"},\"permissions\":[\"LAUNCH\",\"LAUNCH_WEBAPP\",\"APP_TO_APP\",\"CLOSE\",\"TEST_OPEN\",\"TEST_PROTECTED\",\"CONTROL_AUDIO\",\"CONTROL_DISPLAY\",\"CONTROL_INPUT_JOYSTICK\",\"CONTROL_INPUT_MEDIA_RECORDING\",\"CONTROL_INPUT_MEDIA_PLAYBACK\",\"CONTROL_INPUT_TV\",\"CONTROL_POWER\",\"READ_APP_STATUS\",\"READ_CURRENT_CHANNEL\",\"READ_INPUT_DEVICE_LIST\",\"READ_NETWORK_STATE\",\"READ_RUNNING_APPS\",\"READ_TV_CHANNEL_LIST\",\"WRITE_NOTIFICATION_TOAST\",\"READ_POWER_STATE\",\"READ_COUNTRY_INFO\",\"READ_SETTINGS\"],\"signatures\":[{\"signatureVersion\":1,\"signature\":\"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==\"}]}}}"; - private const string AFTER_PAIR_HAND_SHAKE = "{\"type\":\"register\",\"id\":\"register_0\",\"payload\":{\"forcePairing\":false,\"pairingType\":\"PROMPT\",\"client-key\":\"CLIENTKEYGOESHERE\",\"manifest\":{\"manifestVersion\":1,\"appVersion\":\"1.1\",\"signed\":{\"created\":\"20140509\",\"appId\":\"com.lge.test\",\"vendorId\":\"com.lge\",\"localizedAppNames\":{\"\":\"LG Remote App\",\"ko-KR\":\"리모컨 앱\",\"zxx-XX\":\"ЛГ Rэмotэ AПП\"},\"localizedVendorNames\":{\"\":\"LG Electronics\"},\"permissions\":[\"TEST_SECURE\",\"CONTROL_INPUT_TEXT\",\"CONTROL_MOUSE_AND_KEYBOARD\",\"READ_INSTALLED_APPS\",\"READ_LGE_SDX\",\"READ_NOTIFICATIONS\",\"SEARCH\",\"WRITE_SETTINGS\",\"WRITE_NOTIFICATION_ALERT\",\"CONTROL_POWER\",\"READ_CURRENT_CHANNEL\",\"READ_RUNNING_APPS\",\"READ_UPDATE_INFO\",\"UPDATE_FROM_REMOTE_APP\",\"READ_LGE_TV_INPUT_EVENTS\",\"READ_TV_CURRENT_TIME\"],\"serial\":\"2f930e2d2cfe083771f68e4fe7bb07\"},\"permissions\":[\"LAUNCH\",\"LAUNCH_WEBAPP\",\"APP_TO_APP\",\"CLOSE\",\"TEST_OPEN\",\"TEST_PROTECTED\",\"CONTROL_AUDIO\",\"CONTROL_DISPLAY\",\"CONTROL_INPUT_JOYSTICK\",\"CONTROL_INPUT_MEDIA_RECORDING\",\"CONTROL_INPUT_MEDIA_PLAYBACK\",\"CONTROL_INPUT_TV\",\"CONTROL_POWER\",\"READ_APP_STATUS\",\"READ_CURRENT_CHANNEL\",\"READ_INPUT_DEVICE_LIST\",\"READ_NETWORK_STATE\",\"READ_RUNNING_APPS\",\"READ_TV_CHANNEL_LIST\",\"WRITE_NOTIFICATION_TOAST\",\"READ_POWER_STATE\",\"READ_COUNTRY_INFO\"],\"signatures\":[{\"signatureVersion\":1,\"signature\":\"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==\"}]}}}"; - - public Task ControlButton(object oK) - { - // throw new NotImplementedException(); - return null; - } + private const string AFTER_PAIR_HAND_SHAKE = "{\"type\":\"register\",\"id\":\"register_0\",\"payload\":{\"forcePairing\":false,\"pairingType\":\"PROMPT\",\"client-key\":\"CLIENTKEYGOESHERE\",\"manifest\":{\"manifestVersion\":1,\"appVersion\":\"1.1\",\"signed\":{\"created\":\"20140509\",\"appId\":\"com.lge.test\",\"vendorId\":\"com.lge\",\"localizedAppNames\":{\"\":\"LG Remote App\",\"ko-KR\":\"리모컨 앱\",\"zxx-XX\":\"ЛГ Rэмotэ AПП\"},\"localizedVendorNames\":{\"\":\"LG Electronics\"},\"permissions\":[\"TEST_SECURE\",\"CONTROL_INPUT_TEXT\",\"CONTROL_MOUSE_AND_KEYBOARD\",\"READ_INSTALLED_APPS\",\"READ_LGE_SDX\",\"READ_NOTIFICATIONS\",\"SEARCH\",\"WRITE_SETTINGS\",\"WRITE_NOTIFICATION_ALERT\",\"CONTROL_POWER\",\"READ_CURRENT_CHANNEL\",\"READ_RUNNING_APPS\",\"READ_UPDATE_INFO\",\"UPDATE_FROM_REMOTE_APP\",\"READ_LGE_TV_INPUT_EVENTS\",\"READ_TV_CURRENT_TIME\"],\"serial\":\"2f930e2d2cfe083771f68e4fe7bb07\"},\"permissions\":[\"LAUNCH\",\"LAUNCH_WEBAPP\",\"APP_TO_APP\",\"CLOSE\",\"TEST_OPEN\",\"TEST_PROTECTED\",\"CONTROL_AUDIO\",\"CONTROL_DISPLAY\",\"CONTROL_INPUT_JOYSTICK\",\"CONTROL_INPUT_MEDIA_RECORDING\",\"CONTROL_INPUT_MEDIA_PLAYBACK\",\"CONTROL_INPUT_TV\",\"CONTROL_POWER\",\"READ_APP_STATUS\",\"READ_CURRENT_CHANNEL\",\"READ_INPUT_DEVICE_LIST\",\"READ_NETWORK_STATE\",\"READ_RUNNING_APPS\",\"READ_TV_CHANNEL_LIST\",\"WRITE_NOTIFICATION_TOAST\",\"READ_POWER_STATE\",\"READ_COUNTRY_INFO\",\"READ_SETTINGS\"],\"signatures\":[{\"signatureVersion\":1,\"signature\":\"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==\"}]}}}"; private string _currentPairKey; private string webSocketUri; @@ -133,7 +150,7 @@ namespace LgTv public async Task MakeHandShake() { _currentPairKey = _keyStore.GetClientKey(); - if (_currentPairKey != null && !_currentPairKey.All(k => k == '\0')) + if (_currentPairKey != null && !_currentPairKey.All(k => k == '\0') && _keyStore.HasValidHandShake(BEFORE_PAIR_HAND_SHAKE)) { var key = AFTER_PAIR_HAND_SHAKE.Replace("CLIENTKEYGOESHERE", _currentPairKey); var conn = await _connection.SendCommandAsync(key); @@ -143,6 +160,7 @@ namespace LgTv dynamic result = await _connection.SendCommandAsync(BEFORE_PAIR_HAND_SHAKE); _keyStore.SaveClientKey(result.clientKey); + _keyStore.SaveHandShake(BEFORE_PAIR_HAND_SHAKE); } @@ -346,6 +364,22 @@ namespace LgTv await _connection.SubscribeAsync(requestMessage, callback); } + public async Task SubscribePictureSettings(Func callback, dynamic payload = null) + { + if (payload == null) + { + var keys = @"[ ""contrast"", ""backlight"" ]"; + + var json = @"{ ""category"": ""picture"", ""keys"": " + keys + @", ""subscribe"": true }"; + + payload = JObject.Parse(json); + } + + var requestMessage = new RequestMessage("ssap://settings/getSystemSettings", payload, "subscribe"); + + await _connection.SubscribeAsync(requestMessage, callback); + } + public async Task> GetLaunchPoints() { _appList = new List(); @@ -436,11 +470,24 @@ namespace LgTv return (string)response.sessionId; } - public async Task SetSystemSettings(string key, string value, string category = "picture") + public async Task SetSystemSettings(string key, object value, string category = "picture") { + string jsonValue; + var valueType = value.GetType(); + var intType = typeof(int); + if (valueType.IsArray && intType.IsAssignableFrom(valueType.GetElementType())) + { + var values = ((Array)value).Cast(); + jsonValue = "[" + string.Join(", ", values.Select(x => x.ToString()).ToArray()) + ']'; + } + else + { + jsonValue = $"\"{value}\""; + } + var lunauri = "luna://com.webos.settingsservice/setSystemSettings"; - var json = @"{ ""category"": """ + category + @""", ""settings"": { """ + key + @""": """ + value + @""" } }"; + var json = @"{ ""category"": """ + category + @""", ""settings"": { """ + key + @""": " + jsonValue + @" } }"; var @params = JObject.Parse(json); diff --git a/ColorControl/lgtv/LgTvConnection.cs b/ColorControl/lgtv/LgTvConnection.cs index e662fd0..db26586 100644 --- a/ColorControl/lgtv/LgTvConnection.cs +++ b/ColorControl/lgtv/LgTvConnection.cs @@ -5,9 +5,9 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Windows.Networking.Sockets; using Windows.Storage.Streams; -using Windows.Web; using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Linq; +using ColorControl; namespace LgTv { @@ -45,30 +45,29 @@ namespace LgTv _connection.MessageReceived += Connection_MessageReceived; _connection.Closed += Connection_Closed; - var cancellationTokenSource = new CancellationTokenSource(); - var connectTask = _connection.ConnectAsync(uri).AsTask(cancellationTokenSource.Token); - - cancellationTokenSource.CancelAfter(5000); - - var result = await connectTask.ContinueWith((antecedent) => + using (var cancellationTokenSource = new CancellationTokenSource(5000)) { - if (antecedent.Status == TaskStatus.RanToCompletion) + var connectTask = _connection.ConnectAsync(uri).AsTask(cancellationTokenSource.Token); + var result = await connectTask.ContinueWith((antecedent) => { - // connectTask ran to completion, so we know that the MessageWebSocket is connected. - // Add additional code here to use the MessageWebSocket. - IsConnected?.Invoke(true); + if (antecedent.Status == TaskStatus.RanToCompletion) + { + // connectTask ran to completion, so we know that the MessageWebSocket is connected. + // Add additional code here to use the MessageWebSocket. + IsConnected?.Invoke(true); - _messageWriter = new DataWriter(_connection.OutputStream); - return true; - } - else - { - // connectTask timed out, or faulted. - return false; - } - }); + _messageWriter = new DataWriter(_connection.OutputStream); + return true; + } + else + { + // connectTask timed out, or faulted. + return false; + } + }); - return result; + return result; + } } catch (Exception e) { @@ -85,12 +84,22 @@ namespace LgTv } } - public async void SendMessageAsync(string message) + public async Task SendMessageAsync(string message) { try { _messageWriter.WriteString(message); - await _messageWriter.StoreAsync(); + + using (var cancellationTokenSource = new CancellationTokenSource(5000)) + { + await _messageWriter.StoreAsync().AsTask(cancellationTokenSource.Token).ContinueWith((antecedent) => + { + if (antecedent.Status != TaskStatus.RanToCompletion) + { + throw new Exception("SendMessageAsync cancelled due to timeout"); + } + }); + } } catch (Exception e) { @@ -100,24 +109,26 @@ namespace LgTv } } - public Task SendCommandAsync(string message) + public async Task SendCommandAsync(string message) { var obj = JsonConvert.DeserializeObject(message); - return SendCommandAsync((string)obj.id, message); + return await SendCommandAsync((string)obj.id, message); } - private Task SendCommandAsync(string id, string message) + private async Task SendCommandAsync(string id, string message) { try { var taskSource = new TaskCompletionSource(); _tokens.TryAdd(id, taskSource); - SendMessageAsync(message); + await SendMessageAsync(message); if (ConnectionClosed) { throw new Exception("Connection closed"); } - return taskSource.Task; + var result = await taskSource.Task.ConfigureAwait(false); + + return result; } catch (Exception e) { @@ -125,7 +136,7 @@ namespace LgTv } } - public Task SendCommandAsync(RequestMessage message) + public async Task SendCommandAsync(RequestMessage message) { var rawMessage = new RawRequestMessage(message, ++_commandCount); var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings() @@ -133,10 +144,10 @@ namespace LgTv NullValueHandling = NullValueHandling.Ignore, ContractResolver = new CamelCasePropertyNamesContractResolver() }); - return SendCommandAsync(rawMessage.Id, serialized); + return await SendCommandAsync(rawMessage.Id, serialized); } - public Task SubscribeAsync(RequestMessage message, Func callback) + public async Task SubscribeAsync(RequestMessage message, Func callback) { var rawMessage = new RawRequestMessage(message, ++_commandCount); var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings() @@ -147,7 +158,7 @@ namespace LgTv _callbacks.TryAdd(rawMessage.Id, callback); - return SendCommandAsync(rawMessage.Id, serialized); + return await SendCommandAsync(rawMessage.Id, serialized); } private void Connection_Closed(IWebSocket sender, WebSocketClosedEventArgs args) @@ -179,8 +190,7 @@ namespace LgTv } } - else - if (_tokens.TryGetValue(id, out taskCompletion)) + else if (_tokens.TryGetValue(id, out taskCompletion)) { if (id == "register_0") return; if (obj.type == "error") @@ -203,11 +213,37 @@ namespace LgTv catch (Exception ex) { var status = WebSocketError.GetStatus(ex.GetBaseException().HResult); - Logger.Error($"Connection_MessageReceived: status: {status}, exception: {ex.Message}"); + Logger.Error($"Connection_MessageReceived: status: {status}, exception: {ex.ToLogString()}"); + + SetExceptionOnAllTokens(ex); + ConnectionClosed = true; _messageWriter?.Dispose(); } } + + private void SetExceptionOnAllTokens(Exception ex) + { + //Logger.Debug($"Tokens: {_tokens.Count}"); + + foreach (var taskCompletion in _tokens.Values) + { + try + { + if (taskCompletion.Task?.Status == TaskStatus.Running) + { + taskCompletion.SetException(new Exception(ex.Message)); + } + } + catch (Exception ex2) + { + Logger.Error("SetExceptionOnAllTokens: " + ex2.Message); + } + } + + _tokens.Clear(); + } + public void Close() { _connection?.Close(1, ""); diff --git a/ColorControl/packages.config b/ColorControl/packages.config index 22dd691..967ac52 100644 --- a/ColorControl/packages.config +++ b/ColorControl/packages.config @@ -1,11 +1,11 @@  - + - - + + - +