From 3d7abd991ca8952465830ba5584b0386c64f392e Mon Sep 17 00:00:00 2001 From: Vincent Maas Date: Tue, 13 Jun 2023 21:58:00 +0200 Subject: [PATCH] add samsung service, fix some issues --- ColorControl/ColorControl.csproj | 8 + ColorControl/Common/Utils.cs | 27 + ColorControl/Config.cs | 2 + ColorControl/Forms/FormUtils.cs | 12 + ColorControl/MainForm.Designer.cs | 22 +- ColorControl/MainForm.cs | 86 +- ColorControl/MainForm.resx | 62 +- ColorControl/Native/CCD.cs | 215 +++- ColorControl/Program.cs | 16 + ColorControl/Properties/launchSettings.json | 7 + ColorControl/Services/AMD/AmdPanel.cs | 9 +- .../Services/Common/ServiceManager.cs | 3 + .../EventDispatcher/ProcessEventDispatcher.cs | 8 +- .../Services/GameLauncher/GameService.cs | 2 +- ColorControl/Services/LG/LgPanel.cs | 14 +- ColorControl/Services/LG/LgService.cs | 4 +- ColorControl/Services/NVIDIA/NvDitherPanel.cs | 1 + ColorControl/Services/NVIDIA/NvGpuInfo.cs | 14 +- ColorControl/Services/NVIDIA/NvPanel.cs | 9 +- ColorControl/Services/NVIDIA/NvService.cs | 21 +- .../Services/Samsung/RawRequestMessage.cs | 23 + .../Services/Samsung/RequestMessage.cs | 14 + .../Services/Samsung/SamTvConnection.cs | 323 +++++ ColorControl/Services/Samsung/SamsungApp.cs | 19 + .../Services/Samsung/SamsungButtons.cs | 42 + .../Services/Samsung/SamsungDevice.cs | 568 +++++++++ .../Services/Samsung/SamsungPanel.Designer.cs | 840 +++++++++++++ ColorControl/Services/Samsung/SamsungPanel.cs | 1113 +++++++++++++++++ .../Services/Samsung/SamsungPanel.resx | 129 ++ .../Services/Samsung/SamsungPreset.cs | 98 ++ .../Services/Samsung/SamsungService.cs | 676 ++++++++++ .../Services/Samsung/SamsungServiceConfig.cs | 26 + ColorControl/StartUpParams.cs | 14 + ColorControl/lgtv/IsConnectedDelegate.cs | 4 - ColorControl/lgtv/LgTvConnection.cs | 18 +- 35 files changed, 4362 insertions(+), 87 deletions(-) create mode 100644 ColorControl/Properties/launchSettings.json create mode 100644 ColorControl/Services/Samsung/RawRequestMessage.cs create mode 100644 ColorControl/Services/Samsung/RequestMessage.cs create mode 100644 ColorControl/Services/Samsung/SamTvConnection.cs create mode 100644 ColorControl/Services/Samsung/SamsungApp.cs create mode 100644 ColorControl/Services/Samsung/SamsungButtons.cs create mode 100644 ColorControl/Services/Samsung/SamsungDevice.cs create mode 100644 ColorControl/Services/Samsung/SamsungPanel.Designer.cs create mode 100644 ColorControl/Services/Samsung/SamsungPanel.cs create mode 100644 ColorControl/Services/Samsung/SamsungPanel.resx create mode 100644 ColorControl/Services/Samsung/SamsungPreset.cs create mode 100644 ColorControl/Services/Samsung/SamsungService.cs create mode 100644 ColorControl/Services/Samsung/SamsungServiceConfig.cs delete mode 100644 ColorControl/lgtv/IsConnectedDelegate.cs diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index f6f2dbb..5a89eee 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -62,6 +62,9 @@ UserControl + + UserControl + @@ -105,6 +108,11 @@ + + + Designer + + SettingsSingleFileGenerator diff --git a/ColorControl/Common/Utils.cs b/ColorControl/Common/Utils.cs index f16ef94..c2db8e0 100644 --- a/ColorControl/Common/Utils.cs +++ b/ColorControl/Common/Utils.cs @@ -388,6 +388,22 @@ The best and suggested method to provide this is via a Windows Service. Only whe return value; } + public static void SetBrightness(IntPtr handle) + { + var displays = Windows.Graphics.Display.DisplayServices.FindAll(); + + var folderPicker = new Windows.Storage.Pickers.FolderPicker(); + + // Initialize the folder picker with the window handle (HWND). + WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, handle); + + folderPicker.FileTypeFilter.Add("*"); + var folder = folderPicker.PickSingleFolderAsync().GetAwaiter(); + + //var info = Windows.Graphics.Display.DisplayInformation.GetForCurrentView(); + //var colorInfo = info.GetAdvancedColorInfo(); + } + public static async Task> GetPnpDevices(string deviceName) { var devices = new List(); @@ -698,13 +714,24 @@ The best and suggested method to provide this is via a Windows Service. Only whe if (!WinApi.AttachConsole(-1)) { WinApi.AllocConsole(); + + SetConsoleWriter(); + return true; } ConsoleOpened = true; + SetConsoleWriter(); + return false; } + private static void SetConsoleWriter() + { + var writer = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true }; + Console.SetOut(writer); + } + public static bool CloseConsole() { if (!ConsoleOpened) diff --git a/ColorControl/Config.cs b/ColorControl/Config.cs index d1f3642..eef87d7 100644 --- a/ColorControl/Config.cs +++ b/ColorControl/Config.cs @@ -44,6 +44,7 @@ namespace ColorControl public bool UseDedicatedElevatedProcess { get; set; } public ElevationMethod ElevationMethod { get; set; } public bool ElevationMethodAsked { get; set; } + public int ProcessMonitorPollingInterval { get; set; } public List Modules { get; set; } public Config() @@ -59,6 +60,7 @@ namespace ColorControl UseDedicatedElevatedProcess = false; ElevationMethod = ElevationMethod.None; ElevationMethodAsked = false; + ProcessMonitorPollingInterval = 1000; LgPresetsSortState = new ListViewSortState(); NvPresetsSortState = new ListViewSortState(); AmdPresetsSortState = new ListViewSortState(); diff --git a/ColorControl/Forms/FormUtils.cs b/ColorControl/Forms/FormUtils.cs index aafff11..2143f71 100644 --- a/ColorControl/Forms/FormUtils.cs +++ b/ColorControl/Forms/FormUtils.cs @@ -680,6 +680,18 @@ namespace ColorControl.Forms sortState.SortOrder = sorter.Order; sortState.SortIndex = sorter.SortColumn; } + + public static void UpdateShortcutTextBox(TextBox edtShortcut, PresetBase preset) + { + if (preset == null || string.IsNullOrEmpty(edtShortcut.Text)) + { + edtShortcut.ForeColor = CurrentForeColor; + } + else + { + //edtShortcutLg.ForeColor = ShortCutExists(text, preset.id) ? Color.Red : SystemColors.WindowText; + } + } } public class EnumComboBoxItem diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs index 22abec2..86f3b3a 100644 --- a/ColorControl/MainForm.Designer.cs +++ b/ColorControl/MainForm.Designer.cs @@ -70,6 +70,7 @@ label7 = new System.Windows.Forms.Label(); lblInfo = new System.Windows.Forms.Label(); btnUpdate = new System.Windows.Forms.Button(); + btnOptionsAdvanced = new System.Windows.Forms.Button(); tcMain.SuspendLayout(); tabOptions.SuspendLayout(); grpOptionsModules.SuspendLayout(); @@ -113,7 +114,7 @@ // grpOptionsModules.Controls.Add(label1); grpOptionsModules.Controls.Add(chkModules); - grpOptionsModules.Location = new System.Drawing.Point(7, 210); + grpOptionsModules.Location = new System.Drawing.Point(11, 223); grpOptionsModules.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); grpOptionsModules.Name = "grpOptionsModules"; grpOptionsModules.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); @@ -134,8 +135,10 @@ // // chkModules // + chkModules.ColumnWidth = 200; chkModules.FormattingEnabled = true; chkModules.Location = new System.Drawing.Point(7, 40); + chkModules.MultiColumn = true; chkModules.Name = "chkModules"; chkModules.Size = new System.Drawing.Size(507, 76); chkModules.TabIndex = 0; @@ -148,7 +151,7 @@ grpMiscellaneousOptions.Controls.Add(edtBlankScreenSaverShortcut); grpMiscellaneousOptions.Controls.Add(lblFixChromeFontsDescription); grpMiscellaneousOptions.Controls.Add(chkFixChromeFonts); - grpMiscellaneousOptions.Location = new System.Drawing.Point(7, 347); + grpMiscellaneousOptions.Location = new System.Drawing.Point(11, 360); grpMiscellaneousOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); grpMiscellaneousOptions.Name = "grpMiscellaneousOptions"; grpMiscellaneousOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); @@ -213,6 +216,7 @@ // // grpGeneralOptions // + grpGeneralOptions.Controls.Add(btnOptionsAdvanced); grpGeneralOptions.Controls.Add(chkOptionsUseDarkMode); grpGeneralOptions.Controls.Add(chkAutoInstallUpdates); grpGeneralOptions.Controls.Add(btnStartStopService); @@ -232,7 +236,7 @@ grpGeneralOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); grpGeneralOptions.Name = "grpGeneralOptions"; grpGeneralOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - grpGeneralOptions.Size = new System.Drawing.Size(522, 197); + grpGeneralOptions.Size = new System.Drawing.Size(522, 210); grpGeneralOptions.TabIndex = 2; grpGeneralOptions.TabStop = false; grpGeneralOptions.Text = "General"; @@ -587,6 +591,17 @@ btnUpdate.Visible = false; btnUpdate.Click += btnUpdate_Click; // + // btnOptionsAdvanced + // + btnOptionsAdvanced.Location = new System.Drawing.Point(7, 172); + btnOptionsAdvanced.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnOptionsAdvanced.Name = "btnOptionsAdvanced"; + btnOptionsAdvanced.Size = new System.Drawing.Size(88, 27); + btnOptionsAdvanced.TabIndex = 11; + btnOptionsAdvanced.Text = "Advanced..."; + btnOptionsAdvanced.UseVisualStyleBackColor = true; + btnOptionsAdvanced.Click += btnOptionsAdvanced_Click; + // // MainForm // AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -670,6 +685,7 @@ private System.Windows.Forms.CheckedListBox chkModules; private System.Windows.Forms.Label label1; private System.Windows.Forms.CheckBox chkOptionsUseDarkMode; + private System.Windows.Forms.Button btnOptionsAdvanced; } } diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index 92ed828..28b91cb 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -7,6 +7,7 @@ using ColorControl.Services.EventDispatcher; using ColorControl.Services.GameLauncher; using ColorControl.Services.LG; using ColorControl.Services.NVIDIA; +using ColorControl.Services.Samsung; using ColorControl.Svc; using Microsoft.Extensions.DependencyInjection; using NLog; @@ -47,11 +48,13 @@ namespace ColorControl private bool _setVisibleCalled = false; private LgPanel _lgPanel; + private SamsungPanel _samsungPanel; private ToolStripMenuItem _nvTrayMenu; private ToolStripMenuItem _novideoTrayMenu; private ToolStripMenuItem _amdTrayMenu; private ToolStripMenuItem _lgTrayMenu; + private ToolStripMenuItem _samsungTrayMenu; private ToolStripMenuItem _gameTrayMenu; private StartUpParams StartUpParams { get; } @@ -94,6 +97,7 @@ namespace ColorControl _novideoTrayMenu = new ToolStripMenuItem("Novideo sRGB"); _amdTrayMenu = new ToolStripMenuItem("AMD presets"); _lgTrayMenu = new ToolStripMenuItem("LG presets"); + _samsungTrayMenu = new ToolStripMenuItem("Samsung presets"); _gameTrayMenu = new ToolStripMenuItem("Game Launcher"); _trayIcon = new NotifyIcon() { @@ -108,6 +112,7 @@ namespace ColorControl _novideoTrayMenu, _amdTrayMenu, _lgTrayMenu, + _samsungTrayMenu, _gameTrayMenu, new ToolStripSeparator(), new ToolStripMenuItem("Open", null, OpenForm), @@ -163,6 +168,7 @@ namespace ColorControl _modules.Add("AMD controller", InitAmdService); _modules.Add("LG controller", InitLgService); _modules.Add("Game launcher", InitGameService); + _modules.Add("Samsung controller", InitSamsungService); foreach (var keyValue in _modules) { @@ -347,6 +353,31 @@ namespace ColorControl } } + private UserControl InitSamsungService() + { + try + { + _serviceManager.SamsungService = Program.ServiceProvider.GetRequiredService(); + + //_serviceManager.SamsungService.Init(); + + _samsungPanel = new SamsungPanel(_serviceManager.SamsungService, _serviceManager.NvService, _serviceManager.AmdService, Handle); + _samsungPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + + _samsungPanel.Init(); + + InitShortcut(SamsungPanel.SHORTCUTID_SAMSUNGQA, _serviceManager.SamsungService.Config.QuickAccessShortcut, _serviceManager.SamsungService.ToggleQuickAccessForm); + + return _samsungPanel; + } + catch (Exception e) + { + Logger.Error("Error initializing SamsungService: " + e.ToLogString()); + + return null; + } + } + private void TestKey() { BeginInvoke(async () => @@ -499,6 +530,12 @@ namespace ColorControl { _lgPanel?.ApplyLgPreset(lgPreset); } + + var samsungPreset = _serviceManager.SamsungService?.GetPresets().FirstOrDefault(x => x.id == id); + if (samsungPreset != null) + { + _samsungPanel?.ApplyPreset(samsungPreset); + } } } else if (m.Msg == NativeConstants.WM_QUERYENDSESSION) @@ -873,6 +910,16 @@ NOTE: installing the service may cause a User Account Control popup."); UpdateTrayMenu(_lgTrayMenu, presets, TrayMenuItemLg_Click); } + _samsungTrayMenu.Visible = _serviceManager.SamsungService != null; + if (_samsungTrayMenu.Visible) + { + var presets = _serviceManager.SamsungService.GetPresets().Where(x => x.Steps.Any()); + + _samsungTrayMenu.DropDownItems.Clear(); + + UpdateTrayMenu(_samsungTrayMenu, presets, TrayMenuItemSamsung_Click); + } + _gameTrayMenu.Visible = _serviceManager.GameService != null; if (_gameTrayMenu.Visible) { @@ -892,6 +939,14 @@ NOTE: installing the service may cause a User Account Control popup."); await _lgPanel?.ApplyLgPreset(preset); } + private async void TrayMenuItemSamsung_Click(object sender, EventArgs e) + { + var item = sender as ToolStripMenuItem; + var preset = (SamsungPreset)item.Tag; + + await _samsungPanel?.ApplyPreset(preset); + } + private void LoadLog() { var logType = cbxLogType.SelectedIndex; @@ -1311,13 +1366,20 @@ Currently ColorControl is {(Utils.IsAdministrator() ? "" : "not ")}running as ad LoadLog(); } - private void MainForm_Click(object sender, EventArgs e) + private async void MainForm_Click(object sender, EventArgs e) { //_powerEventDispatcher.SendEvent(PowerEventDispatcher.Event_Shutdown); //PipeUtils.SendMessage(SvcMessageType.RestartAfterUpdate); //Program.Restart(); //Environment.Exit(0); //InstallUpdate(""); + //await Test(); + + //Utils.SetBrightness(Handle); + } + + private async Task Test() + { } private void SetTheme(bool toDark) @@ -1417,5 +1479,27 @@ Currently ColorControl is {(Utils.IsAdministrator() ? "" : "not ")}running as ad SetTheme(_config.UseDarkMode); } + + private void btnOptionsAdvanced_Click(object sender, EventArgs e) + { + var processPollingIntervalField = new MessageForms.FieldDefinition + { + FieldType = MessageForms.FieldType.Numeric, + Label = "Polling interval of process monitor (milliseconds).", + SubLabel = "Decreasing this delay may execute triggered presets sooner but can cause a higher CPU load", + MinValue = 50, + MaxValue = 5000, + Value = _config.ProcessMonitorPollingInterval + }; + + var values = MessageForms.ShowDialog("Advanced settings", new[] { processPollingIntervalField }); + + if (values?.Any() != true) + { + return; + } + + _config.ProcessMonitorPollingInterval = processPollingIntervalField.ValueAsInt; + } } } \ No newline at end of file diff --git a/ColorControl/MainForm.resx b/ColorControl/MainForm.resx index dc5a7dd..5d463d4 100644 --- a/ColorControl/MainForm.resx +++ b/ColorControl/MainForm.resx @@ -1,4 +1,64 @@ - + + + diff --git a/ColorControl/Native/CCD.cs b/ColorControl/Native/CCD.cs index 4f58eab..286bce4 100644 --- a/ColorControl/Native/CCD.cs +++ b/ColorControl/Native/CCD.cs @@ -8,6 +8,8 @@ namespace ColorControl // This class takes care of wrapping "Connecting and Configuring Displays(CCD) Win32 API" internal static class CCD { + private static bool? IsHDRActive = null; + public enum DisplayTopology { Internal, @@ -59,7 +61,7 @@ namespace ColorControl } } - public static void SetHDRState(bool enabled, string displayName = null) + public static void SetHDRState(bool enabled, string displayName = null, float? SDRWhiteLevelInNits = null) { uint pathCount, modeCount; @@ -76,21 +78,7 @@ namespace ColorControl { 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)) + if (!MatchDisplayNames(path, displayName)) { continue; } @@ -98,12 +86,12 @@ namespace ColorControl 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(); ; + 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(); ; + requestpacket.header.size = Marshal.SizeOf(); for (int i = 0; i < modeCount; i++) { @@ -114,26 +102,189 @@ namespace ColorControl requestpacket.header.adapterId = modesArray[i].adapterId; requestpacket.header.id = modesArray[i].id; - if (NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket) == NativeConstants.ERROR_SUCCESS) + if (NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket) == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorSupported) { - if (requestpacket.advancedColorSupported) - { - setpacket.enableAdvancedColor = enabled ? 1U : 0; - NativeMethods.DisplayConfigSetDeviceInfo(ref setpacket); - } + setpacket.enableAdvancedColor = enabled ? 1U : 0; + NativeMethods.DisplayConfigSetDeviceInfo(ref setpacket); + + IsHDRActive = enabled; + + //if (enabled && SDRWhiteLevelInNits.HasValue) + //{ + // SetSDRWhiteLevel(modesArray[i], SDRWhiteLevelInNits.Value); + //} } } } } } } - + if (err != NativeConstants.ERROR_SUCCESS) { throw new Win32Exception(err); } } + public static void SetSDRWhiteLevel(float SDRWhiteLevelInNits, 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) + { + if (!MatchDisplayNames(path, displayName)) + { + 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) + { + requestpacket.header.adapterId = modesArray[i].adapterId; + requestpacket.header.id = modesArray[i].id; + + err = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket); + + if (err == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorSupported) + { + SetSDRWhiteLevel(modesArray[i], SDRWhiteLevelInNits); + } + } + } + } + } + } + + if (err != NativeConstants.ERROR_SUCCESS) + { + throw new Win32Exception(err); + } + } + + public static bool IsHDREnabled(string displayName = null) + { + if (IsHDRActive.HasValue) + { + return IsHDRActive.Value; + } + + 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]; + + err = NativeMethods.QueryDisplayConfig(QueryDisplayFlags.DatabaseCurrent, ref pathCount, pathsArray, ref modeCount, modesArray, out _); + if (err == NativeConstants.ERROR_SUCCESS) + { + foreach (var path in pathsArray) + { + if (!MatchDisplayNames(path, displayName)) + { + 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) + { + requestpacket.header.adapterId = modesArray[i].adapterId; + requestpacket.header.id = modesArray[i].id; + + err = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket); + + IsHDRActive = err == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorEnabled; + + return IsHDRActive.Value; + } + } + } + } + } + + return false; + } + + private static bool MatchDisplayNames(DisplayConfigPathInfo path, string displayName) + { + if (displayName == null) + { + return true; + } + + // 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; + + var err = NativeMethods.DisplayConfigGetDeviceInfo(ref info); + if (err != NativeConstants.ERROR_SUCCESS) + { + return false; + } + + var deviceName = info.viewGdiDeviceName; + if (!EqualDisplayNames(deviceName, displayName)) + { + return false; + } + + return true; + } + + private static void SetSDRWhiteLevel(DisplayConfigModeInfo displayConfigModeInfo, float value) + { + var setpacket = new DISPLAYCONFIG_SDR_WHITE_LEVEL(); + setpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER(); + setpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL; + setpacket.header.size = Marshal.SizeOf(); + + var requestpacket = new DISPLAYCONFIG_SDR_WHITE_LEVEL(); + requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER(); + requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + requestpacket.header.size = Marshal.SizeOf(); + requestpacket.header.adapterId = displayConfigModeInfo.adapterId; + requestpacket.header.id = displayConfigModeInfo.id; + + var error = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket); + + if (error == NativeConstants.ERROR_SUCCESS) + { + setpacket.header.adapterId = displayConfigModeInfo.adapterId; + setpacket.header.id = displayConfigModeInfo.id; + setpacket.SDRWhiteLevel = 2000; + + error = NativeMethods.DisplayConfigSetDeviceInfo(ref setpacket); + + } + } + private static bool EqualDisplayNames(string displayName1, string displayName2) { displayName1 = displayName1.Replace("\\", string.Empty); @@ -162,6 +313,7 @@ namespace ColorControl DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9, DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10, DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11, + DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL = 12, } [StructLayout(LayoutKind.Sequential)] @@ -506,6 +658,13 @@ namespace ColorControl public uint enableAdvancedColor; } + [StructLayout(LayoutKind.Sequential)] + private struct DISPLAYCONFIG_SDR_WHITE_LEVEL + { + public DISPLAYCONFIG_DEVICE_INFO_HEADER header; + public uint SDRWhiteLevel; + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS { @@ -559,6 +718,12 @@ namespace ColorControl [DllImport("user32")] public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DEVICE_NAME requestPacket); + + [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); } } } diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs index fb031c8..3354a1c 100644 --- a/ColorControl/Program.cs +++ b/ColorControl/Program.cs @@ -6,6 +6,7 @@ using ColorControl.Services.EventDispatcher; using ColorControl.Services.GameLauncher; using ColorControl.Services.LG; using ColorControl.Services.NVIDIA; +using ColorControl.Services.Samsung; using ColorControl.Svc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -290,6 +291,7 @@ namespace ColorControl Console.WriteLine("--nvpreset : execute NVIDIA-preset"); Console.WriteLine("--amdpreset : execute AMD-preset"); Console.WriteLine("--lgpreset : execute LG-preset"); + Console.WriteLine("--sampreset : execute Samsung-preset"); Console.WriteLine("--help : displays this help info"); Console.WriteLine("Options:"); Console.WriteLine("--nogui: starts command from the command line and will not open GUI (is forced when GUI is already running)"); @@ -309,6 +311,18 @@ namespace ColorControl result = true; } + if (startUpParams.ExecuteSamsungPreset) + { + Utils.OpenConsole(); + + Console.WriteLine($"Executing Samsung-preset '{startUpParams.SamsungPresetName}'..."); + await SamsungService.ExecutePresetAsync(startUpParams.SamsungPresetName); + + Console.WriteLine("Done."); + + result = true; + } + if (startUpParams.ExecuteNvidiaPreset) { Utils.OpenConsole(); @@ -358,10 +372,12 @@ namespace ColorControl services.RegisterSharedServices(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); }); } private static async Task RunService(string[] args) diff --git a/ColorControl/Properties/launchSettings.json b/ColorControl/Properties/launchSettings.json new file mode 100644 index 0000000..b2d85e7 --- /dev/null +++ b/ColorControl/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "ColorControl": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/ColorControl/Services/AMD/AmdPanel.cs b/ColorControl/Services/AMD/AmdPanel.cs index 86719a8..60eda62 100644 --- a/ColorControl/Services/AMD/AmdPanel.cs +++ b/ColorControl/Services/AMD/AmdPanel.cs @@ -268,14 +268,7 @@ namespace ColorControl.Services.AMD var preset = GetSelectedAmdPreset(); - if (preset == null || string.IsNullOrEmpty(text)) - { - edtAmdShortcut.ForeColor = SystemColors.WindowText; - } - else - { - // edtAmdShortcut.ForeColor = ShortCutExists(text, preset.id) ? Color.Red : SystemColors.WindowText; - } + FormUtils.UpdateShortcutTextBox(edtAmdShortcut, preset); } private void btnSetAmdShortcut_Click(object sender, EventArgs e) diff --git a/ColorControl/Services/Common/ServiceManager.cs b/ColorControl/Services/Common/ServiceManager.cs index b36eb7c..ccbf9ad 100644 --- a/ColorControl/Services/Common/ServiceManager.cs +++ b/ColorControl/Services/Common/ServiceManager.cs @@ -3,6 +3,7 @@ using ColorControl.Services.AMD; using ColorControl.Services.GameLauncher; using ColorControl.Services.LG; using ColorControl.Services.NVIDIA; +using ColorControl.Services.Samsung; using System; using System.Linq; using System.Threading.Tasks; @@ -15,6 +16,7 @@ namespace ColorControl.Services.Common internal LgService LgService { get; set; } internal AmdService AmdService { get; set; } internal GameService GameService { get; set; } + internal SamsungService SamsungService { get; set; } public async Task HandleExternalServiceAsync(string serviceName, string[] parameters) { @@ -58,6 +60,7 @@ namespace ColorControl.Services.Common AmdService?.GlobalSave(); LgService?.GlobalSave(); GameService?.GlobalSave(); + SamsungService?.GlobalSave(); } } } diff --git a/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs b/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs index 5e4cf87..c6c5ffa 100644 --- a/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs +++ b/ColorControl/Services/EventDispatcher/ProcessEventDispatcher.cs @@ -1,5 +1,6 @@ using ColorControl.Common; using ColorControl.Forms; +using ColorControl.Services.Common; using System; using System.Collections.Generic; using System.Diagnostics; @@ -31,9 +32,12 @@ namespace ColorControl.Services.EventDispatcher protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); private ProcessChangedEventArgs MonitorContext { get; set; } + private AppContextProvider _appContextProvider { get; } - public ProcessEventDispatcher() + public ProcessEventDispatcher(AppContextProvider appContextProvider) { + _appContextProvider = appContextProvider; + IsRunning = true; //Task.Run(async () => await CheckProcesses()); @@ -53,7 +57,7 @@ namespace ColorControl.Services.EventDispatcher while (IsRunning) { - await Task.Delay(1000); + await Task.Delay(_appContextProvider.GetAppContext().Config.ProcessMonitorPollingInterval); if (!HasHandlers(Event_ProcessChanged)) { diff --git a/ColorControl/Services/GameLauncher/GameService.cs b/ColorControl/Services/GameLauncher/GameService.cs index 38d9d50..86c0c0c 100644 --- a/ColorControl/Services/GameLauncher/GameService.cs +++ b/ColorControl/Services/GameLauncher/GameService.cs @@ -9,7 +9,7 @@ namespace ColorControl.Services.GameLauncher { class GameService : ServiceBase { - private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + //private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); public override string ServiceName => "Game"; diff --git a/ColorControl/Services/LG/LgPanel.cs b/ColorControl/Services/LG/LgPanel.cs index 775f194..398e4f0 100644 --- a/ColorControl/Services/LG/LgPanel.cs +++ b/ColorControl/Services/LG/LgPanel.cs @@ -10,7 +10,6 @@ using NStandard; using System; using System.Collections.Generic; using System.ComponentModel; -using System.Drawing; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -724,14 +723,7 @@ You can also activate this option by using the Expert-button and selecting Wake- var preset = GetSelectedLgPreset(); - if (preset == null || string.IsNullOrEmpty(text)) - { - edtShortcutLg.ForeColor = SystemColors.WindowText; - } - else - { - //edtShortcutLg.ForeColor = ShortCutExists(text, preset.id) ? Color.Red : SystemColors.WindowText; - } + FormUtils.UpdateShortcutTextBox(edtShortcutLg, preset); } private async void btnLgAddDevice_Click(object sender, EventArgs e) @@ -747,7 +739,7 @@ You can also activate this option by using the Expert-button and selecting Wake- Enabled = false; try { - await device.TestConnection(); + result = await device.TestConnection(); } finally { @@ -1185,7 +1177,7 @@ Do you want to continue?"; new() { FieldType = MessageForms.FieldType.Numeric, - Label = "Delay when shutting down/restarting PC (milli seconds).", + Label = "Delay when shutting down/restarting PC (milliseconds).", SubLabel = "This delay may prevent the tv from powering off when restarting the pc.", MinValue = 0, MaxValue = 5000, diff --git a/ColorControl/Services/LG/LgService.cs b/ColorControl/Services/LG/LgService.cs index edff430..6e446c9 100644 --- a/ColorControl/Services/LG/LgService.cs +++ b/ColorControl/Services/LG/LgService.cs @@ -75,7 +75,7 @@ namespace ColorControl.Services.LG private RestartDetector _restartDetector; private SynchronizationContext _syncContext; - public LgService(AppContextProvider appContextProvider, PowerEventDispatcher powerEventDispatcher, SessionSwitchDispatcher sessionSwitchDispatcher, ProcessEventDispatcher processEventDispatcher, ServiceManager serviceManager) : base(appContextProvider) + public LgService(AppContextProvider appContextProvider, PowerEventDispatcher powerEventDispatcher, SessionSwitchDispatcher sessionSwitchDispatcher, ProcessEventDispatcher processEventDispatcher, ServiceManager serviceManager, RestartDetector restartDetector) : base(appContextProvider) { _allowPowerOn = appContextProvider.GetAppContext().StartUpParams.RunningFromScheduledTask; _powerEventDispatcher = powerEventDispatcher; @@ -84,7 +84,7 @@ namespace ColorControl.Services.LG _serviceManager = serviceManager; LgPreset.LgApps = _lgApps; - _restartDetector = new RestartDetector(); + _restartDetector = restartDetector; _syncContext = appContextProvider.GetAppContext().SynchronizationContext; LgTvApiCore.SyncContext = _syncContext; diff --git a/ColorControl/Services/NVIDIA/NvDitherPanel.cs b/ColorControl/Services/NVIDIA/NvDitherPanel.cs index cd5b007..dca66eb 100644 --- a/ColorControl/Services/NVIDIA/NvDitherPanel.cs +++ b/ColorControl/Services/NVIDIA/NvDitherPanel.cs @@ -42,6 +42,7 @@ namespace ColorControl.Services.NVIDIA var primaryDisplayInfo = displays.FirstOrDefault(d => d.Display == primaryDisplay); var index = primaryDisplayInfo != null ? displays.IndexOf(primaryDisplayInfo) : 0; + cbxDitheringDisplay.Items.Clear(); cbxDitheringDisplay.Items.AddRange(displays.ToArray()); if (cbxDitheringDisplay.SelectedIndex == -1) diff --git a/ColorControl/Services/NVIDIA/NvGpuInfo.cs b/ColorControl/Services/NVIDIA/NvGpuInfo.cs index 8dce388..ee72835 100644 --- a/ColorControl/Services/NVIDIA/NvGpuInfo.cs +++ b/ColorControl/Services/NVIDIA/NvGpuInfo.cs @@ -192,6 +192,11 @@ namespace ColorControl.Services.NVIDIA var powerLimits = GetPowerLimits(); + if (!DefaultPowerInMilliWatts.HasValue) + { + return; + } + MinPowerInMilliWatts = (int)((decimal)DefaultPowerInMilliWatts / 100000 * powerLimits.Item1); MaxPowerInMilliWatts = (int)((decimal)DefaultPowerInMilliWatts / 100000 * powerLimits.Item2); } @@ -245,10 +250,13 @@ namespace ColorControl.Services.NVIDIA { if (DefaultPowerInMilliWatts.HasValue) { - var powerInPCM = GPU.PowerTopologyInformation.PowerTopologyEntries.FirstOrDefault(e => e.Domain == PowerTopologyDomain.Board); - var power = (uint)((decimal)powerInPCM.PowerUsageInPCM / 100000 * DefaultPowerInMilliWatts); + return Logger.Swallow(() => + { + var powerInPCM = GPU.PowerTopologyInformation.PowerTopologyEntries.FirstOrDefault(e => e.Domain == PowerTopologyDomain.Board); + var power = (uint)((decimal)powerInPCM.PowerUsageInPCM / 100000 * DefaultPowerInMilliWatts); - return power; + return power; + }); } return 0; diff --git a/ColorControl/Services/NVIDIA/NvPanel.cs b/ColorControl/Services/NVIDIA/NvPanel.cs index 5098efc..f7d93ef 100644 --- a/ColorControl/Services/NVIDIA/NvPanel.cs +++ b/ColorControl/Services/NVIDIA/NvPanel.cs @@ -946,14 +946,7 @@ namespace ColorControl.Services.NVIDIA var preset = GetSelectedNvPreset(); - if (preset == null || string.IsNullOrEmpty(text)) - { - edtShortcut.ForeColor = SystemColors.WindowText; - } - else - { - //edtShortcut.ForeColor = ShortCutExists(text, preset.id) ? Color.Red : SystemColors.WindowText; - } + FormUtils.UpdateShortcutTextBox(edtShortcut, preset); } private void miNvResolutionIncluded_Click(object sender, EventArgs e) diff --git a/ColorControl/Services/NVIDIA/NvService.cs b/ColorControl/Services/NVIDIA/NvService.cs index 861d82f..157ad56 100644 --- a/ColorControl/Services/NVIDIA/NvService.cs +++ b/ColorControl/Services/NVIDIA/NvService.cs @@ -309,6 +309,11 @@ namespace ColorControl.Services.NVIDIA SetHDRState(display, newHdrEnabled); } + if (hdrEnabled) + { + //CCD.SetSDRWhiteLevel(1000, display.Name); + } + if (preset.applyRefreshRate || preset.applyResolution) { if (!SetMode(preset.applyResolution ? preset.resolutionWidth : 0, preset.applyResolution ? preset.resolutionHeight : 0, preset.applyRefreshRate ? preset.refreshRate : 0, true)) @@ -619,7 +624,7 @@ namespace ColorControl.Services.NVIDIA resolutionHeight = (uint)desktopRect.Height; } - var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation); + var portrait = IsDisplayInPortraitMode(display); return SetRefreshRateInternal(display.Name, (int)refreshRate, portrait, (int)resolutionWidth, (int)resolutionHeight, updateRegistry); } @@ -637,12 +642,24 @@ namespace ColorControl.Services.NVIDIA return new List(); } - var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation); + var portrait = IsDisplayInPortraitMode(display); var desktopRect = display.DisplayDevice.ScanOutInformation.SourceDesktopRectangle; return GetAvailableRefreshRatesInternal(display.Name, portrait, desktopRect.Width, desktopRect.Height); } + private bool IsDisplayInPortraitMode(NvAPIWrapper.Display.Display display) + { + try + { + return new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation); + } + catch (Exception) + { + return false; + } + } + public List GetAvailableResolutions(NvPreset preset = null) { if (preset != null) diff --git a/ColorControl/Services/Samsung/RawRequestMessage.cs b/ColorControl/Services/Samsung/RawRequestMessage.cs new file mode 100644 index 0000000..1ce3032 --- /dev/null +++ b/ColorControl/Services/Samsung/RawRequestMessage.cs @@ -0,0 +1,23 @@ + +using Newtonsoft.Json; + +namespace ColorControl.Services.Samsung +{ + internal class RawRequestMessage + { + public RawRequestMessage(RequestMessage rm, int commandCount) + { + Id = commandCount.ToString(); + Method = rm.Method; + Params = rm.Params; + } + + [JsonIgnore] + public string Id { get; set; } + + [JsonProperty("method")] + public string Method { get; set; } + [JsonProperty("params")] + public object Params { get; set; } + } +} \ No newline at end of file diff --git a/ColorControl/Services/Samsung/RequestMessage.cs b/ColorControl/Services/Samsung/RequestMessage.cs new file mode 100644 index 0000000..df4a3d3 --- /dev/null +++ b/ColorControl/Services/Samsung/RequestMessage.cs @@ -0,0 +1,14 @@ +namespace ColorControl.Services.Samsung +{ + public class RequestMessage + { + public RequestMessage(string method, object payload) + { + Method = method; + Params = payload; + } + + public string Method { get; set; } + public object Params { get; set; } + } +} \ No newline at end of file diff --git a/ColorControl/Services/Samsung/SamTvConnection.cs b/ColorControl/Services/Samsung/SamTvConnection.cs new file mode 100644 index 0000000..258f9f6 --- /dev/null +++ b/ColorControl/Services/Samsung/SamTvConnection.cs @@ -0,0 +1,323 @@ +using ColorControl.Common; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Windows.Networking.Sockets; +using Windows.Security.Cryptography.Certificates; +using Windows.Storage.Streams; + +namespace ColorControl.Services.Samsung +{ + public class SendMessageException : Exception + { + public SendMessageException(string message, Exception e) : base(message, e) + { + + } + } + + public delegate void ConnectedDelegate(string token, bool disconnect); + + public class SamTvConnection : IDisposable + { + public static SynchronizationContext SyncContext { get; set; } + + public bool ConnectionClosed { get; private set; } + public bool ClosedByDispose { get; private set; } + + private MessageWebSocket _connection; + private DataWriter _messageWriter; + private int _commandCount; + private int _readTimeout; + + public event ConnectedDelegate Connected; + private readonly ConcurrentDictionary> _tokens = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _callbacks = new ConcurrentDictionary>(); + + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + public async Task Connect(Uri uri, int readTimeout = 5000) + { + _readTimeout = readTimeout; + + try + { + _connection?.Dispose(); + + ConnectionClosed = false; + _commandCount = 0; + + // Not creating the web socket from main thread can cause a stackoverflow in combase32.dll + if (Environment.CurrentManagedThreadId != 1) + { + SyncContext.Send((_) => CreateWebSocket(), null); + } + else + { + CreateWebSocket(); + } + + try + { + await _connection.ConnectAsync(uri).AsTask().WaitAsync(TimeSpan.FromSeconds(5)); + + _messageWriter = new DataWriter(_connection.OutputStream); + + return true; + } + catch (TimeoutException te) + { + Logger.Error($"Connect to {uri}: {te.Message}"); + return false; + } + } + catch (Exception e) + { + switch (SocketError.GetStatus(e.HResult)) + { + case SocketErrorStatus.HostNotFound: + // Handle HostNotFound Error + break; + default: + // Handle Unknown Error + break; + } + + Logger.Error($"Connect to {uri}: {e.Message}"); + return false; + } + } + + private void CreateWebSocket() + { + _connection = new MessageWebSocket(); + _connection.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted); + _connection.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName); + _connection.Control.MessageType = SocketMessageType.Utf8; + _connection.MessageReceived += Connection_MessageReceived; + _connection.Closed += Connection_Closed; + } + + public async Task SendMessageAsync(string message) + { + try + { + if (_messageWriter == null) + { + throw new Exception("SendMessageAsync: no writer"); + } + + _messageWriter.WriteString(message); + + await _messageWriter.StoreAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(5)); + } + catch (Exception e) + { + Logger.Error("SendMessageAsync: " + e.Message); + ConnectionClosed = true; + _messageWriter?.Dispose(); + } + } + + private async Task SendCommandAsync(string id, string message, bool waitForAnswer = true) + { + try + { + if (waitForAnswer) + { + var taskSource = new TaskCompletionSource(); + _tokens.TryAdd(id, taskSource); + + await SendMessageAsync(message); + if (ConnectionClosed) + { + throw new Exception("Connection closed"); + } + + return await taskSource.Task; + } + else + { + await SendMessageAsync(message); + if (ConnectionClosed) + { + throw new Exception("Connection closed"); + } + + return null; + } + + } + catch (Exception e) + { + throw new SendMessageException("Can't send message", e); + } + } + + public async Task SendCommandAsync(RequestMessage message, bool waitForAnswer = true) + { + var rawMessage = new RawRequestMessage(message, ++_commandCount); + var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + //ContractResolver = new CamelCasePropertyNamesContractResolver() + }); + return await SendCommandAsync(rawMessage.Id, serialized, waitForAnswer); + } + + public async Task SubscribeAsync(RequestMessage message, Func callback) + { + var rawMessage = new RawRequestMessage(message, ++_commandCount); + var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver() + }); + + _callbacks.TryAdd(rawMessage.Id, callback); + + return await SendCommandAsync(rawMessage.Id, serialized); + } + + private void Connection_Closed(IWebSocket sender, WebSocketClosedEventArgs args) + { + var webSocket = Interlocked.Exchange(ref _connection, null); + webSocket?.Dispose(); + ConnectionClosed = true; + + Logger.Debug("Connection closed"); + + Connected?.Invoke(null, true); + } + + private const string MS_CHANNEL_CONNECT_EVENT = "ms.channel.connect"; + private const string MS_CHANNEL_CLIENT_CONNECT_EVENT = "ms.channel.clientConnect"; + private const string MS_CHANNEL_CLIENT_DISCONNECT_EVENT = "ms.channel.clientDisconnect"; + private const string MS_CHANNEL_READY_EVENT = "ms.channel.ready"; + private const string MS_CHANNEL_UNAUTHORIZED = "ms.channel.unauthorized"; + private const string MS_ERROR_EVENT = "ms.error"; + + private async void Connection_MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args) + { + try + { + var task = new Task(args.GetDataReader); + task.Start(); + var dataReader = await task.WaitAsync(TimeSpan.FromMilliseconds(_readTimeout)); + + using (dataReader) + { + dataReader.UnicodeEncoding = UnicodeEncoding.Utf8; + var message = dataReader.ReadString(dataReader.UnconsumedBufferLength); + var obj = JsonConvert.DeserializeObject(message); + var id = (string)obj.id; + var type = (string)obj.@event; + + Logger.Debug($"Received message: {message}"); + + // Response: + // {"data":{"clients":[{"attributes":{"name":"c2Ftc3VuZ2N0bA=="},"connectTime":1685302606520,"deviceName":"c2Ftc3VuZ2N0bA==","id":"92e0cbc1-6f86-4013-9b9b-65a47afb2236","isHost":false}],"id":"92e0cbc1-6f86-4013-9b9b-65a47afb2236","token":"16224598"},"event":"ms.channel.connect"} + // Second response: + // {"data":{"clients":[{"attributes":{"name":"c2Ftc3VuZ2N0bA==","token":"16224598"},"connectTime":1685303321571,"deviceName":"c2Ftc3VuZ2N0bA==","id":"58589451-2ecf-44f0-9267-31983a5466","isHost":false}],"id":"58589451-2ecf-44f0-9267-31983a5466"},"event":"ms.channel.connect"} + + //TaskCompletionSource taskCompletion; + if (type == MS_CHANNEL_CONNECT_EVENT) + { + var token = (string)obj.data?.token; + + if (token == null) + { + token = (string)obj.data?.clients[0]?.attributes?.token; + } + + if (token != null) + { + Connected?.Invoke(token, false); + } + + //if (_tokens.TryRemove(id, out taskCompletion)) + //{ + // var key = (string)JObject.Parse(message)["payload"]["client-key"]; + // taskCompletion.TrySetResult(new { clientKey = key }); + //} + + } + //else if (_tokens.TryGetValue(id, out taskCompletion)) + //{ + // if (id == "register_0") return; + // if (obj.type == "error") + // { + // taskCompletion.SetException(new Exception(obj.error?.ToString())); + // } + // //else if (args.Cancelled) + // //{ + // // taskSource.SetCanceled(); + // //} + // taskCompletion.TrySetResult(obj.payload); + + // if (_callbacks.TryGetValue(id, out Func callback)) + // { + // try + // { + // callback(obj.payload); + // } + // catch (Exception callbackException) + // { + // Logger.Error($"Connection_MessageReceived: the callback threw an exception: {callbackException.ToLogString()}"); + // } + // } + //} + } + } + catch (Exception ex) + { + var status = WebSocketError.GetStatus(ex.GetBaseException().HResult); + 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.RanToCompletion) + { + Logger.Debug("taskCompletion.Task.Status: " + taskCompletion.Task.Status); + taskCompletion.SetException(new Exception(ex.Message)); + } + } + catch (Exception ex2) + { + Logger.Error("SetExceptionOnAllTokens: " + ex2.Message); + } + } + + _tokens.Clear(); + } + + public void Close() + { + _connection?.Close(1000, ""); + } + + public void Dispose() + { + ClosedByDispose = true; + Close(); + _connection?.Dispose(); + } + } +} diff --git a/ColorControl/Services/Samsung/SamsungApp.cs b/ColorControl/Services/Samsung/SamsungApp.cs new file mode 100644 index 0000000..d5235f6 --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungApp.cs @@ -0,0 +1,19 @@ +namespace ColorControl.Services.Samsung +{ + public class SamsungApp + { + public string Title { get; set; } + public string AppId { get; set; } + + public SamsungApp(string appId, string title) + { + AppId = appId; + Title = title; + } + + public override string ToString() + { + return Title; + } + } +} diff --git a/ColorControl/Services/Samsung/SamsungButtons.cs b/ColorControl/Services/Samsung/SamsungButtons.cs new file mode 100644 index 0000000..e3a9f08 --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungButtons.cs @@ -0,0 +1,42 @@ +namespace ColorControl.Services.Samsung +{ + public enum ButtonType + { + KEY_POWER, + KEY_UP, + KEY_DOWN, + KEY_LEFT, + KEY_RIGHT, + KEY_CHUP, + KEY_CHDOWN, + KEY_ENTER, + KEY_RETURN, + KEY_CH_LIST, + KEY_MENU, + KEY_SOURCE, + KEY_GUIDE, + KEY_TOOLS, + KEY_INFO, + KEY_RED, + KEY_GREEN, + KEY_YELLOW, + KEY_BLUE, + KEY_PANNEL_CHDOWN, + KEY_VOLUP, + KEY_VOLDOWN, + KEY_MUTE, + KEY_0, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_DTV, + KEY_HDMI, + KEY_CONTENTS + } +} diff --git a/ColorControl/Services/Samsung/SamsungDevice.cs b/ColorControl/Services/Samsung/SamsungDevice.cs new file mode 100644 index 0000000..cab3133 --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungDevice.cs @@ -0,0 +1,568 @@ +using ColorControl.Common; +using ColorControl.Services.Common; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using NLog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ColorControl.Services.Samsung +{ + class SamsungDeviceOptions + { + public bool PowerOnAfterStartup { get; set; } + public bool PowerOnAfterResume { get; set; } + public bool PowerOffOnShutdown { get; set; } + public bool PowerOffOnStandby { get; set; } + public bool PowerOffOnScreenSaver { get; set; } + public int ScreenSaverMinimalDuration { get; set; } + public bool TurnScreenOffOnScreenSaver { get; set; } + public bool HandleManualScreenSaver { get; set; } + public bool PowerOnAfterScreenSaver { get; set; } + public bool TurnScreenOnAfterScreenSaver { get; set; } + public bool PowerOnAfterManualPowerOff { get; set; } + public bool PowerOnByWindows { get; set; } + public bool PowerOffByWindows { get; set; } + public bool TriggersEnabled { get; set; } + public int HDMIPortNumber { get; set; } + + public bool UseSecureConnection { get; set; } = true; + } + + public enum PowerOffSource + { + Unknown, + App, + External + } + + class SamsungDevice + { + public class InvokableAction + { + public Func, Task> AsyncFunction { get; set; } + public string Name { get; set; } + public Type EnumType { get; set; } + public decimal MinValue { get; set; } + public decimal MaxValue { get; set; } + public string Category { get; set; } + public string Title { get; set; } + public int CurrentValue { get; set; } + public int NumberOfValues { get; set; } + public bool Advanced { get; set; } + public List ValueLabels { get; set; } + } + + + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + public string Name { get; private set; } + public string IpAddress { get; private set; } + public string MacAddress { get; private set; } + public bool IsCustom { get; private set; } + public string Token { get; set; } + public SamsungDeviceOptions Options { get; set; } + + [JsonIgnore] + public bool IsDummy { get; private set; } + + [JsonIgnore] + public bool PoweredOn { get; private set; } + public DateTimeOffset PoweredOffAt { get; private set; } + public PowerOffSource PoweredOffBy { get; internal set; } + + [JsonIgnore] + private SamTvConnection _samTvConnection; + + [JsonIgnore] + private ServiceManager _serviceManager; + [JsonIgnore] + private Timer _powerOffTimer; + [JsonIgnore] + private SemaphoreSlim _connectSemaphore = new SemaphoreSlim(1); + [JsonIgnore] + private TaskCompletionSource _taskCompletionSource; + + public SamsungDevice(string name, string ipAddress, string macAddress, bool isCustom = true, bool isDummy = false) + { + Options = new SamsungDeviceOptions(); + + Name = name; + IpAddress = ipAddress; + MacAddress = macAddress; + IsCustom = isCustom; + IsDummy = isDummy; + + _serviceManager = Program.ServiceProvider.GetRequiredService(); + } + + ~SamsungDevice() + { + ClearPowerOffTask(); + } + + public async Task ConnectAsync(bool force = false, bool retryToken = false) + { + await _connectSemaphore.WaitAsync(); + try + { + if (force) + { + _samTvConnection?.Dispose(); + _samTvConnection = null; + } + else if (IsConnected()) + { + return true; + } + + _samTvConnection = new SamTvConnection(); + _samTvConnection.Connected += Connection_Connected; + var b64 = Utils.Base64Encode("samsungctl"); + + var firstTime = Token == null; + var readTimeout = firstTime ? 60000 : 5000; + + try + { + var protocol = Options.UseSecureConnection ? "wss" : "ws"; + var port = Options.UseSecureConnection ? 8002 : 8001; + + var uriString = $"{protocol}://{IpAddress}:{port}/api/v2/channels/samsung.remote.control?name={b64}"; + + if (Token != null) + { + uriString += $"&token={Token}"; + } + + var result = await _samTvConnection.Connect(new Uri(uriString), readTimeout); + + if (!result) + { + return false; + } + + var waitTime = Token != null ? 3000 : 60000; + + _taskCompletionSource = new TaskCompletionSource(); + + await _taskCompletionSource.Task.WaitAsync(TimeSpan.FromMilliseconds(waitTime)); + + _taskCompletionSource = null; + + //if (Token != null) + //{ + // await Task.Delay(1500); + //} + //else + //{ + // var seconds = 60; + // while (seconds > 0 && Token == null) + // { + // await Task.Delay(1000); + // seconds--; + // } + //} + + if (firstTime && Token != null && !retryToken) + { + _samTvConnection.Dispose(); + _samTvConnection = null; + + return await ConnectAsync(force, true); + } + + return true; + } + catch (Exception ex) + { + Logger.Debug(ex); + + return false; + } + + } + finally + { + _connectSemaphore.Release(); + } + } + + private void Connection_Connected(string token, bool disconnect) + { + if (disconnect) + { + Logger.Debug($"Disconnect event, powered off by: {PoweredOffBy}"); + + if (_samTvConnection?.ClosedByDispose != true && PoweredOffBy == PowerOffSource.Unknown) + { + PoweredOffBy = PowerOffSource.External; + } + + return; + } + + Token = token; + + _taskCompletionSource?.SetResult(token); + PoweredOffBy = PowerOffSource.Unknown; + } + + internal async Task PowerOffAsync() + { + await SendKeyAsync("KEY_POWER"); + + PoweredOn = false; + PoweredOffAt = DateTimeOffset.Now; + PoweredOffBy = PowerOffSource.App; + } + + internal async Task WakeAndConnectWithRetries(int retries = 5) + { + var maxRetries = retries <= 1 ? 5 : retries; + + var result = false; + for (var retry = 0; retry < maxRetries && !result; retry++) + { + Logger.Debug($"WakeAndConnectWithRetries: attempt {retry + 1} of {maxRetries}..."); + + var ms = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + result = await WakeAndConnect(); + ms = DateTimeOffset.Now.ToUnixTimeMilliseconds() - ms; + if (!result) + { + var delay = 2000 - ms; + if (delay > 0) + { + await Task.Delay((int)delay); + } + } + } + + return result; + } + + internal async Task WakeAndConnect(int connectDelay = 500) + { + try + { + var result = Utils.PingHost(IpAddress); + + Logger.Debug($"Ping result: {result}"); + + var powerState = default(string); + + if (result) + { + powerState = await GetPowerStateAsync(); + } + + if (result && powerState == "on" && IsConnected()) + { + Logger.Debug("Already powered on and connected"); + return true; + } + + if (!result || string.IsNullOrEmpty(powerState)) + { + result = Wake(); + if (!result) + { + Logger.Debug("WOL failed"); + return false; + } + Logger.Debug("WOL succeeded"); + await Task.Delay(connectDelay); + } + + result = await ConnectAsync(true); + + if (result && powerState == "standby") + { + await SendKeyAsync("KEY_POWER"); + //var powerState = await GetPowerStateAsync(); + + ////if (!PoweredOn && (PoweredOffAt == DateTimeOffset.MinValue || PoweredOffAt >= DateTimeOffset.Now.AddMinutes(-2))) + //if (powerState == "standby") + //{ + // await SendKeyAsync("KEY_POWER"); + //} + } + + PoweredOn = true; + + Logger.Debug("Connect succeeded: " + result); + return result; + } + catch (Exception e) + { + Logger.Error("WakeAndConnectToSelectedDevice: " + e.ToLogString()); + return false; + } + } + + internal bool Wake() + { + var result = false; + + if (MacAddress != null) + { + result = WOL.WakeFunctionCheckAdmin(MacAddress, IpAddress); + //_justWokeUp = true; + } + else + { + Logger.Debug("Cannot wake device: the device has no MAC-address"); + } + + return result; + } + + public async Task GetPowerStateAsync() + { + var url = $"http://{IpAddress}:8001/api/v2/"; + + var json = await Utils.GetRestJsonAsync(url); + + if (json == null) + { + Logger.Error("No power state received"); + + return string.Empty; + } + + var powerState = (string)json.device?.PowerState; + + Logger.Error($"Current power state: {powerState}"); + + return powerState; + } + + internal async Task TestConnectionAsync() + { + var result = Utils.PingHost(IpAddress); + + if (result) + { + return await ConnectAsync(); + } + + return false; + } + + public bool IsConnected() + { + return _samTvConnection?.ConnectionClosed == false; + } + + internal void ConvertToCustom() + { + IsCustom = true; + } + + public override string ToString() + { + return $"{(IsDummy ? string.Empty : (IsCustom ? "Custom: " : "Auto detect: "))}{Name}{(!string.IsNullOrEmpty(IpAddress) ? ", " + IpAddress : string.Empty)}"; + } + + internal async Task ExecutePresetAsync(SamsungPreset preset, AppContext appContext, SamsungServiceConfig config) + { + var hasApp = !string.IsNullOrEmpty(preset.AppId); + + var hasWOL = preset.Steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase)); + + if (hasWOL) + { + var connected = await WakeAndConnect(); + if (!connected) + { + return false; + } + } + + for (var tries = 0; tries <= 1; tries++) + { + if (!await ConnectAsync()) + { + return false; + } + + if (hasApp) + { + try + { + //await _lgTvApi.LaunchApp(preset.appId, @params); + } + catch (Exception ex) + { + string logMessage = ex.ToLogString(Environment.StackTrace); + Logger.Error("Error while launching app: " + logMessage); + + if (tries == 0) + { + continue; + } + return false; + } + + await Task.Delay(1000); + } + + if (preset.Steps.Any()) + { + if (hasApp) + { + await Task.Delay(1500); + } + try + { + await ExecuteStepsAsync(preset, config); + } + catch (Exception ex) + { + string logMessage = ex.ToLogString(Environment.StackTrace); + Logger.Error("Error while executing steps: " + logMessage); + + if (tries == 0) + { + continue; + } + return false; + } + } + + return true; + } + + return true; + } + + private async Task ExecuteStepsAsync(SamsungPreset preset, SamsungServiceConfig config) + { + foreach (var step in preset.Steps) + { + var keySpec = step.Split(':'); + + var delay = config.DefaultButtonDelay; + var key = step; + if (keySpec.Length == 2) + { + delay = Utils.ParseInt(keySpec[1]); + if (delay > 0) + { + key = keySpec[0]; + } + } + + var index = key.IndexOf("("); + string[] parameters = null; + if (index > -1) + { + var keyValue = key.Split('('); + key = keyValue[0]; + parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Split(';'); + } + + var executeKey = true; + if (parameters != null && await _serviceManager.HandleExternalServiceAsync(key, parameters)) + { + executeKey = false; + } + + if (executeKey) + { + await SendKeyAsync(key); + delay = delay == 0 ? config.DefaultButtonDelay : delay; + } + + if (delay > 0) + { + await Task.Delay(delay); + } + } + } + + private async Task SendKeyAsync(string key) + { + var dynamic = new + { + Cmd = "Click", + DataOfCmd = key, + Option = "false", + TypeOfRemote = "SendRemoteKey" + }; + + var message = new RequestMessage("ms.remote.control", dynamic); + + await _samTvConnection.SendCommandAsync(message, false); + } + + internal void ClearPowerOffTask() + { + _powerOffTimer?.Dispose(); + } + + internal void PowerOffIn(int seconds) + { + ClearPowerOffTask(); + + _powerOffTimer = new Timer(PowerOffByTimer); + _powerOffTimer.Change(TimeSpan.FromSeconds(seconds), TimeSpan.FromSeconds(seconds)); + } + + private void PowerOffByTimer(object _) + { + ClearPowerOffTask(); + + var x = PerformActionOnScreenSaver(); + } + + private bool IsPowerOffAllowed() => true; + + public async Task PerformActionOnScreenSaver() + { + if (!IsPowerOffAllowed()) + { + return true; + } + + if (Options.TurnScreenOffOnScreenSaver) + { + Logger.Debug("Turning screen off on screen saver"); + //await _lgTvApi.TurnScreenOff(); + + return true; + } + + Logger.Debug($"Device {Name} is now powering off due to delayed screensaver task"); + + await PowerOffAsync(); + + return true; + } + + public async Task PerformActionAfterScreenSaver(int powerOnRetries) + { + if (Options.TurnScreenOffOnScreenSaver && IsConnected()) + { + if (Options.TurnScreenOnAfterScreenSaver) + { + try + { + Logger.Debug("Turning screen on after screen saver"); + //await _lgTvApi.TurnScreenOn(); + } + catch (Exception) { } + } + + return true; + } + + return await WakeAndConnectWithRetries(powerOnRetries); + } + } +} + diff --git a/ColorControl/Services/Samsung/SamsungPanel.Designer.cs b/ColorControl/Services/Samsung/SamsungPanel.Designer.cs new file mode 100644 index 0000000..7779aba --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungPanel.Designer.cs @@ -0,0 +1,840 @@ +namespace ColorControl.Services.Samsung +{ + partial class SamsungPanel + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + scSamsungController = new System.Windows.Forms.SplitContainer(); + btnSamsungDeviceOptionsSettings = new System.Windows.Forms.Button(); + btnSamsungSettings = new System.Windows.Forms.Button(); + chkSamsungQuickAccess = new System.Windows.Forms.CheckBox(); + cbxSamsungPcHdmiPort = new System.Windows.Forms.ComboBox(); + lblSamsungPcHdmiPort = new System.Windows.Forms.Label(); + edtSamsungPresetDescription = new System.Windows.Forms.TextBox(); + lblSamsungPresetDescription = new System.Windows.Forms.Label(); + btnSamsungDeviceOptionsHelp = new System.Windows.Forms.Button(); + btnSamsungPresetEditTriggerConditions = new System.Windows.Forms.Button(); + edtSamsungPresetTriggerConditions = new System.Windows.Forms.TextBox(); + lblSamsungPresetExcludedProcesses = new System.Windows.Forms.Label(); + edtSamsungPresetExcludedProcesses = new System.Windows.Forms.TextBox(); + lblSamsungPresetIncludedProcesses = new System.Windows.Forms.Label(); + edtSamsungPresetIncludedProcesses = new System.Windows.Forms.TextBox(); + lblSamsungPresetTriggerCondition = new System.Windows.Forms.Label(); + cbxSamsungPresetTrigger = new System.Windows.Forms.ComboBox(); + lblSamsungPresetTrigger = new System.Windows.Forms.Label(); + chkSamsungRemoteControlShow = new System.Windows.Forms.CheckBox(); + label3 = new System.Windows.Forms.Label(); + cbxSamsungDevices = new System.Windows.Forms.ComboBox(); + btnSamsungDeviceConvertToCustom = new System.Windows.Forms.Button(); + cbxSamsungApps = new System.Windows.Forms.ComboBox(); + lvSamsungPresets = new System.Windows.Forms.ListView(); + label5 = new System.Windows.Forms.Label(); + cbxSamsungPresetDevice = new System.Windows.Forms.ComboBox(); + btnDeleteLg = new System.Windows.Forms.Button(); + btnApplyLg = new System.Windows.Forms.Button(); + lblDeviceFilter = new System.Windows.Forms.Label(); + lblSamsungPresetDevice = new System.Windows.Forms.Label(); + btnAddLg = new System.Windows.Forms.Button(); + edtShortcutLg = new System.Windows.Forms.TextBox(); + edtSamsungDeviceFilter = new System.Windows.Forms.TextBox(); + btnSamsungRemoveDevice = new System.Windows.Forms.Button(); + btnSamsungRefreshApps = new System.Windows.Forms.Button(); + label2 = new System.Windows.Forms.Label(); + label1 = new System.Windows.Forms.Label(); + btnSamsungAddDevice = new System.Windows.Forms.Button(); + edtNameLg = new System.Windows.Forms.TextBox(); + btnSetShortcutLg = new System.Windows.Forms.Button(); + edtStepsLg = new System.Windows.Forms.TextBox(); + btnSamsungAddButton = new System.Windows.Forms.Button(); + mnuLgButtons = new System.Windows.Forms.ContextMenuStrip(components); + mnuLgRcButtons = new System.Windows.Forms.ToolStripMenuItem(); + mnuLgActions = new System.Windows.Forms.ToolStripMenuItem(); + mnuLgNvPresets = new System.Windows.Forms.ToolStripMenuItem(); + mnuLgAmdPresets = new System.Windows.Forms.ToolStripMenuItem(); + mnuLgProgram = new System.Windows.Forms.ToolStripMenuItem(); + btnCloneLg = new System.Windows.Forms.Button(); + lblStepsLg = new System.Windows.Forms.Label(); + btnSamsungDeviceFilterRefresh = new System.Windows.Forms.Button(); + clbLgPower = new System.Windows.Forms.CheckedListBox(); + lblSamsungError = new System.Windows.Forms.Label(); + mnuLgSettings = new System.Windows.Forms.ContextMenuStrip(components); + miTestPowerOffOn = new System.Windows.Forms.ToolStripMenuItem(); + miNvSettings = new System.Windows.Forms.ToolStripMenuItem(); + ((System.ComponentModel.ISupportInitialize)scSamsungController).BeginInit(); + scSamsungController.Panel1.SuspendLayout(); + scSamsungController.SuspendLayout(); + mnuLgButtons.SuspendLayout(); + mnuLgSettings.SuspendLayout(); + SuspendLayout(); + // + // scSamsungController + // + scSamsungController.Dock = System.Windows.Forms.DockStyle.Fill; + scSamsungController.Location = new System.Drawing.Point(0, 0); + scSamsungController.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + scSamsungController.Name = "scSamsungController"; + // + // scSamsungController.Panel1 + // + scSamsungController.Panel1.Controls.Add(btnSamsungDeviceOptionsSettings); + scSamsungController.Panel1.Controls.Add(btnSamsungSettings); + scSamsungController.Panel1.Controls.Add(chkSamsungQuickAccess); + scSamsungController.Panel1.Controls.Add(cbxSamsungPcHdmiPort); + scSamsungController.Panel1.Controls.Add(lblSamsungPcHdmiPort); + scSamsungController.Panel1.Controls.Add(edtSamsungPresetDescription); + scSamsungController.Panel1.Controls.Add(lblSamsungPresetDescription); + scSamsungController.Panel1.Controls.Add(btnSamsungDeviceOptionsHelp); + scSamsungController.Panel1.Controls.Add(btnSamsungPresetEditTriggerConditions); + scSamsungController.Panel1.Controls.Add(edtSamsungPresetTriggerConditions); + scSamsungController.Panel1.Controls.Add(lblSamsungPresetExcludedProcesses); + scSamsungController.Panel1.Controls.Add(edtSamsungPresetExcludedProcesses); + scSamsungController.Panel1.Controls.Add(lblSamsungPresetIncludedProcesses); + scSamsungController.Panel1.Controls.Add(edtSamsungPresetIncludedProcesses); + scSamsungController.Panel1.Controls.Add(lblSamsungPresetTriggerCondition); + scSamsungController.Panel1.Controls.Add(cbxSamsungPresetTrigger); + scSamsungController.Panel1.Controls.Add(lblSamsungPresetTrigger); + scSamsungController.Panel1.Controls.Add(chkSamsungRemoteControlShow); + scSamsungController.Panel1.Controls.Add(label3); + scSamsungController.Panel1.Controls.Add(cbxSamsungDevices); + scSamsungController.Panel1.Controls.Add(btnSamsungDeviceConvertToCustom); + scSamsungController.Panel1.Controls.Add(cbxSamsungApps); + scSamsungController.Panel1.Controls.Add(lvSamsungPresets); + scSamsungController.Panel1.Controls.Add(label5); + scSamsungController.Panel1.Controls.Add(cbxSamsungPresetDevice); + scSamsungController.Panel1.Controls.Add(btnDeleteLg); + scSamsungController.Panel1.Controls.Add(btnApplyLg); + scSamsungController.Panel1.Controls.Add(lblDeviceFilter); + scSamsungController.Panel1.Controls.Add(lblSamsungPresetDevice); + scSamsungController.Panel1.Controls.Add(btnAddLg); + scSamsungController.Panel1.Controls.Add(edtShortcutLg); + scSamsungController.Panel1.Controls.Add(edtSamsungDeviceFilter); + scSamsungController.Panel1.Controls.Add(btnSamsungRemoveDevice); + scSamsungController.Panel1.Controls.Add(btnSamsungRefreshApps); + scSamsungController.Panel1.Controls.Add(label2); + scSamsungController.Panel1.Controls.Add(label1); + scSamsungController.Panel1.Controls.Add(btnSamsungAddDevice); + scSamsungController.Panel1.Controls.Add(edtNameLg); + scSamsungController.Panel1.Controls.Add(btnSetShortcutLg); + scSamsungController.Panel1.Controls.Add(edtStepsLg); + scSamsungController.Panel1.Controls.Add(btnSamsungAddButton); + scSamsungController.Panel1.Controls.Add(btnCloneLg); + scSamsungController.Panel1.Controls.Add(lblStepsLg); + scSamsungController.Panel1.Controls.Add(btnSamsungDeviceFilterRefresh); + scSamsungController.Panel1.Controls.Add(clbLgPower); + scSamsungController.Panel1.Controls.Add(lblSamsungError); + scSamsungController.Panel1MinSize = 750; + scSamsungController.Panel2MinSize = 150; + scSamsungController.Size = new System.Drawing.Size(1114, 539); + scSamsungController.SplitterDistance = 899; + scSamsungController.SplitterWidth = 5; + scSamsungController.TabIndex = 43; + // + // btnSamsungDeviceOptionsSettings + // + btnSamsungDeviceOptionsSettings.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + btnSamsungDeviceOptionsSettings.Enabled = false; + btnSamsungDeviceOptionsSettings.Location = new System.Drawing.Point(872, 103); + btnSamsungDeviceOptionsSettings.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungDeviceOptionsSettings.Name = "btnSamsungDeviceOptionsSettings"; + btnSamsungDeviceOptionsSettings.Size = new System.Drawing.Size(22, 27); + btnSamsungDeviceOptionsSettings.TabIndex = 65; + btnSamsungDeviceOptionsSettings.Text = "S"; + btnSamsungDeviceOptionsSettings.UseVisualStyleBackColor = true; + btnSamsungDeviceOptionsSettings.Click += btnSamsungDeviceOptionsSettings_Click; + // + // btnSamsungSettings + // + btnSamsungSettings.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + btnSamsungSettings.Location = new System.Drawing.Point(807, 284); + btnSamsungSettings.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungSettings.Name = "btnSamsungSettings"; + btnSamsungSettings.Size = new System.Drawing.Size(88, 27); + btnSamsungSettings.TabIndex = 64; + btnSamsungSettings.Text = "Settings..."; + btnSamsungSettings.UseVisualStyleBackColor = true; + btnSamsungSettings.Click += btnSamsungSettings_Click; + // + // chkSamsungQuickAccess + // + chkSamsungQuickAccess.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + chkSamsungQuickAccess.AutoSize = true; + chkSamsungQuickAccess.Enabled = false; + chkSamsungQuickAccess.Location = new System.Drawing.Point(341, 320); + chkSamsungQuickAccess.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + chkSamsungQuickAccess.Name = "chkSamsungQuickAccess"; + chkSamsungQuickAccess.Size = new System.Drawing.Size(96, 19); + chkSamsungQuickAccess.TabIndex = 63; + chkSamsungQuickAccess.Text = "Quick Access"; + chkSamsungQuickAccess.UseVisualStyleBackColor = true; + // + // cbxSamsungPcHdmiPort + // + cbxSamsungPcHdmiPort.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + cbxSamsungPcHdmiPort.FormattingEnabled = true; + cbxSamsungPcHdmiPort.Items.AddRange(new object[] { "None", "HDMI1", "HDMI2", "HDMI3", "HDMI4" }); + cbxSamsungPcHdmiPort.Location = new System.Drawing.Point(667, 42); + cbxSamsungPcHdmiPort.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxSamsungPcHdmiPort.Name = "cbxSamsungPcHdmiPort"; + cbxSamsungPcHdmiPort.Size = new System.Drawing.Size(86, 23); + cbxSamsungPcHdmiPort.TabIndex = 62; + cbxSamsungPcHdmiPort.SelectedIndexChanged += cbxSamsungPcHdmiPort_SelectedIndexChanged; + // + // lblSamsungPcHdmiPort + // + lblSamsungPcHdmiPort.AutoSize = true; + lblSamsungPcHdmiPort.Location = new System.Drawing.Point(577, 45); + lblSamsungPcHdmiPort.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungPcHdmiPort.Name = "lblSamsungPcHdmiPort"; + lblSamsungPcHdmiPort.Size = new System.Drawing.Size(84, 15); + lblSamsungPcHdmiPort.TabIndex = 61; + lblSamsungPcHdmiPort.Text = "PC HDMI port:"; + // + // edtSamsungPresetDescription + // + edtSamsungPresetDescription.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + edtSamsungPresetDescription.Enabled = false; + edtSamsungPresetDescription.Location = new System.Drawing.Point(100, 489); + edtSamsungPresetDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtSamsungPresetDescription.Multiline = true; + edtSamsungPresetDescription.Name = "edtSamsungPresetDescription"; + edtSamsungPresetDescription.Size = new System.Drawing.Size(699, 43); + edtSamsungPresetDescription.TabIndex = 60; + // + // lblSamsungPresetDescription + // + lblSamsungPresetDescription.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + lblSamsungPresetDescription.AutoSize = true; + lblSamsungPresetDescription.Location = new System.Drawing.Point(6, 494); + lblSamsungPresetDescription.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungPresetDescription.Name = "lblSamsungPresetDescription"; + lblSamsungPresetDescription.Size = new System.Drawing.Size(70, 15); + lblSamsungPresetDescription.TabIndex = 59; + lblSamsungPresetDescription.Text = "Description:"; + // + // btnSamsungDeviceOptionsHelp + // + btnSamsungDeviceOptionsHelp.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + btnSamsungDeviceOptionsHelp.Location = new System.Drawing.Point(872, 70); + btnSamsungDeviceOptionsHelp.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungDeviceOptionsHelp.Name = "btnSamsungDeviceOptionsHelp"; + btnSamsungDeviceOptionsHelp.Size = new System.Drawing.Size(22, 27); + btnSamsungDeviceOptionsHelp.TabIndex = 58; + btnSamsungDeviceOptionsHelp.Text = "?"; + btnSamsungDeviceOptionsHelp.UseVisualStyleBackColor = true; + btnSamsungDeviceOptionsHelp.Click += btnSamsungDeviceOptionsHelp_Click; + // + // btnSamsungPresetEditTriggerConditions + // + btnSamsungPresetEditTriggerConditions.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + btnSamsungPresetEditTriggerConditions.Enabled = false; + btnSamsungPresetEditTriggerConditions.Location = new System.Drawing.Point(804, 347); + btnSamsungPresetEditTriggerConditions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungPresetEditTriggerConditions.Name = "btnSamsungPresetEditTriggerConditions"; + btnSamsungPresetEditTriggerConditions.Size = new System.Drawing.Size(88, 27); + btnSamsungPresetEditTriggerConditions.TabIndex = 54; + btnSamsungPresetEditTriggerConditions.Text = "Edit..."; + btnSamsungPresetEditTriggerConditions.UseVisualStyleBackColor = true; + btnSamsungPresetEditTriggerConditions.Click += btnSamsungPresetEditTriggerConditions_Click; + // + // edtSamsungPresetTriggerConditions + // + edtSamsungPresetTriggerConditions.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + edtSamsungPresetTriggerConditions.Enabled = false; + edtSamsungPresetTriggerConditions.Location = new System.Drawing.Point(601, 349); + edtSamsungPresetTriggerConditions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtSamsungPresetTriggerConditions.Name = "edtSamsungPresetTriggerConditions"; + edtSamsungPresetTriggerConditions.ReadOnly = true; + edtSamsungPresetTriggerConditions.Size = new System.Drawing.Size(194, 23); + edtSamsungPresetTriggerConditions.TabIndex = 53; + // + // lblSamsungPresetExcludedProcesses + // + lblSamsungPresetExcludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + lblSamsungPresetExcludedProcesses.AutoSize = true; + lblSamsungPresetExcludedProcesses.Location = new System.Drawing.Point(475, 414); + lblSamsungPresetExcludedProcesses.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungPresetExcludedProcesses.Name = "lblSamsungPresetExcludedProcesses"; + lblSamsungPresetExcludedProcesses.Size = new System.Drawing.Size(112, 15); + lblSamsungPresetExcludedProcesses.TabIndex = 52; + lblSamsungPresetExcludedProcesses.Text = "Excluded processes:"; + // + // edtSamsungPresetExcludedProcesses + // + edtSamsungPresetExcludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + edtSamsungPresetExcludedProcesses.Enabled = false; + edtSamsungPresetExcludedProcesses.Location = new System.Drawing.Point(601, 411); + edtSamsungPresetExcludedProcesses.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtSamsungPresetExcludedProcesses.Name = "edtSamsungPresetExcludedProcesses"; + edtSamsungPresetExcludedProcesses.Size = new System.Drawing.Size(289, 23); + edtSamsungPresetExcludedProcesses.TabIndex = 51; + // + // lblSamsungPresetIncludedProcesses + // + lblSamsungPresetIncludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + lblSamsungPresetIncludedProcesses.AutoSize = true; + lblSamsungPresetIncludedProcesses.Location = new System.Drawing.Point(475, 384); + lblSamsungPresetIncludedProcesses.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungPresetIncludedProcesses.Name = "lblSamsungPresetIncludedProcesses"; + lblSamsungPresetIncludedProcesses.Size = new System.Drawing.Size(110, 15); + lblSamsungPresetIncludedProcesses.TabIndex = 50; + lblSamsungPresetIncludedProcesses.Text = "Included processes:"; + // + // edtSamsungPresetIncludedProcesses + // + edtSamsungPresetIncludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + edtSamsungPresetIncludedProcesses.Enabled = false; + edtSamsungPresetIncludedProcesses.Location = new System.Drawing.Point(601, 381); + edtSamsungPresetIncludedProcesses.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtSamsungPresetIncludedProcesses.Name = "edtSamsungPresetIncludedProcesses"; + edtSamsungPresetIncludedProcesses.Size = new System.Drawing.Size(289, 23); + edtSamsungPresetIncludedProcesses.TabIndex = 49; + // + // lblSamsungPresetTriggerCondition + // + lblSamsungPresetTriggerCondition.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + lblSamsungPresetTriggerCondition.AutoSize = true; + lblSamsungPresetTriggerCondition.Location = new System.Drawing.Point(475, 353); + lblSamsungPresetTriggerCondition.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungPresetTriggerCondition.Name = "lblSamsungPresetTriggerCondition"; + lblSamsungPresetTriggerCondition.Size = new System.Drawing.Size(63, 15); + lblSamsungPresetTriggerCondition.TabIndex = 47; + lblSamsungPresetTriggerCondition.Text = "Condition:"; + // + // cbxSamsungPresetTrigger + // + cbxSamsungPresetTrigger.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + cbxSamsungPresetTrigger.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + cbxSamsungPresetTrigger.Enabled = false; + cbxSamsungPresetTrigger.FormattingEnabled = true; + cbxSamsungPresetTrigger.Location = new System.Drawing.Point(601, 318); + cbxSamsungPresetTrigger.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxSamsungPresetTrigger.Name = "cbxSamsungPresetTrigger"; + cbxSamsungPresetTrigger.Size = new System.Drawing.Size(289, 23); + cbxSamsungPresetTrigger.TabIndex = 46; + // + // lblSamsungPresetTrigger + // + lblSamsungPresetTrigger.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + lblSamsungPresetTrigger.AutoSize = true; + lblSamsungPresetTrigger.Location = new System.Drawing.Point(475, 321); + lblSamsungPresetTrigger.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungPresetTrigger.Name = "lblSamsungPresetTrigger"; + lblSamsungPresetTrigger.Size = new System.Drawing.Size(46, 15); + lblSamsungPresetTrigger.TabIndex = 45; + lblSamsungPresetTrigger.Text = "Trigger:"; + // + // chkSamsungRemoteControlShow + // + chkSamsungRemoteControlShow.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + chkSamsungRemoteControlShow.Appearance = System.Windows.Forms.Appearance.Button; + chkSamsungRemoteControlShow.AutoSize = true; + chkSamsungRemoteControlShow.Location = new System.Drawing.Point(793, 8); + chkSamsungRemoteControlShow.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + chkSamsungRemoteControlShow.Name = "chkSamsungRemoteControlShow"; + chkSamsungRemoteControlShow.Size = new System.Drawing.Size(101, 25); + chkSamsungRemoteControlShow.TabIndex = 43; + chkSamsungRemoteControlShow.Text = "Remote Control"; + chkSamsungRemoteControlShow.UseVisualStyleBackColor = true; + chkSamsungRemoteControlShow.CheckedChanged += chkSamsungRemoteControlShow_CheckedChanged; + // + // label3 + // + label3.AutoSize = true; + label3.Location = new System.Drawing.Point(6, 14); + label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label3.Name = "label3"; + label3.Size = new System.Drawing.Size(45, 15); + label3.TabIndex = 31; + label3.Text = "Device:"; + // + // cbxSamsungDevices + // + cbxSamsungDevices.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + cbxSamsungDevices.DropDownWidth = 400; + cbxSamsungDevices.FormattingEnabled = true; + cbxSamsungDevices.Location = new System.Drawing.Point(100, 9); + cbxSamsungDevices.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxSamsungDevices.Name = "cbxSamsungDevices"; + cbxSamsungDevices.Size = new System.Drawing.Size(370, 23); + cbxSamsungDevices.TabIndex = 30; + cbxSamsungDevices.SelectedIndexChanged += cbxSamsungDevices_SelectedIndexChanged; + // + // btnSamsungDeviceConvertToCustom + // + btnSamsungDeviceConvertToCustom.Location = new System.Drawing.Point(667, 8); + btnSamsungDeviceConvertToCustom.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungDeviceConvertToCustom.Name = "btnSamsungDeviceConvertToCustom"; + btnSamsungDeviceConvertToCustom.Size = new System.Drawing.Size(88, 27); + btnSamsungDeviceConvertToCustom.TabIndex = 41; + btnSamsungDeviceConvertToCustom.Text = "To custom..."; + btnSamsungDeviceConvertToCustom.UseVisualStyleBackColor = true; + btnSamsungDeviceConvertToCustom.Click += btnSamsungDeviceConvertToCustom_Click; + // + // cbxSamsungApps + // + cbxSamsungApps.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + cbxSamsungApps.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + cbxSamsungApps.Enabled = false; + cbxSamsungApps.FormattingEnabled = true; + cbxSamsungApps.Location = new System.Drawing.Point(100, 380); + cbxSamsungApps.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxSamsungApps.Name = "cbxSamsungApps"; + cbxSamsungApps.Size = new System.Drawing.Size(233, 23); + cbxSamsungApps.TabIndex = 26; + cbxSamsungApps.KeyPress += cbxSamsungApps_KeyPress; + // + // lvSamsungPresets + // + lvSamsungPresets.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + lvSamsungPresets.CheckBoxes = true; + lvSamsungPresets.FullRowSelect = true; + lvSamsungPresets.Location = new System.Drawing.Point(6, 152); + lvSamsungPresets.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + lvSamsungPresets.MultiSelect = false; + lvSamsungPresets.Name = "lvSamsungPresets"; + lvSamsungPresets.ShowItemToolTips = true; + lvSamsungPresets.Size = new System.Drawing.Size(888, 126); + lvSamsungPresets.TabIndex = 8; + lvSamsungPresets.UseCompatibleStateImageBehavior = false; + lvSamsungPresets.View = System.Windows.Forms.View.Details; + lvSamsungPresets.ColumnClick += lvSamsungPresets_ColumnClick; + lvSamsungPresets.ItemChecked += lvSamsungPresets_ItemChecked; + lvSamsungPresets.SelectedIndexChanged += lvSamsungPresets_SelectedIndexChanged; + lvSamsungPresets.DoubleClick += lvSamsungPresets_DoubleClick; + // + // label5 + // + label5.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + label5.AutoSize = true; + label5.Location = new System.Drawing.Point(6, 384); + label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label5.Name = "label5"; + label5.Size = new System.Drawing.Size(32, 15); + label5.TabIndex = 25; + label5.Text = "App:"; + // + // cbxSamsungPresetDevice + // + cbxSamsungPresetDevice.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + cbxSamsungPresetDevice.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + cbxSamsungPresetDevice.Enabled = false; + cbxSamsungPresetDevice.FormattingEnabled = true; + cbxSamsungPresetDevice.Location = new System.Drawing.Point(100, 348); + cbxSamsungPresetDevice.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + cbxSamsungPresetDevice.Name = "cbxSamsungPresetDevice"; + cbxSamsungPresetDevice.Size = new System.Drawing.Size(370, 23); + cbxSamsungPresetDevice.TabIndex = 40; + // + // btnDeleteLg + // + btnDeleteLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + btnDeleteLg.Enabled = false; + btnDeleteLg.Location = new System.Drawing.Point(289, 284); + btnDeleteLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnDeleteLg.Name = "btnDeleteLg"; + btnDeleteLg.Size = new System.Drawing.Size(88, 27); + btnDeleteLg.TabIndex = 27; + btnDeleteLg.Text = "Delete"; + btnDeleteLg.UseVisualStyleBackColor = true; + btnDeleteLg.Click += btnDeleteLg_Click; + // + // btnApplyLg + // + btnApplyLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + btnApplyLg.Enabled = false; + btnApplyLg.Location = new System.Drawing.Point(6, 284); + btnApplyLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnApplyLg.Name = "btnApplyLg"; + btnApplyLg.Size = new System.Drawing.Size(88, 27); + btnApplyLg.TabIndex = 9; + btnApplyLg.Text = "Apply"; + btnApplyLg.UseVisualStyleBackColor = true; + btnApplyLg.Click += btnApplyLg_Click; + // + // lblDeviceFilter + // + lblDeviceFilter.AutoSize = true; + lblDeviceFilter.Location = new System.Drawing.Point(6, 45); + lblDeviceFilter.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblDeviceFilter.Name = "lblDeviceFilter"; + lblDeviceFilter.Size = new System.Drawing.Size(72, 15); + lblDeviceFilter.TabIndex = 24; + lblDeviceFilter.Text = "Device filter:"; + // + // lblSamsungPresetDevice + // + lblSamsungPresetDevice.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + lblSamsungPresetDevice.AutoSize = true; + lblSamsungPresetDevice.Location = new System.Drawing.Point(6, 351); + lblSamsungPresetDevice.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungPresetDevice.Name = "lblSamsungPresetDevice"; + lblSamsungPresetDevice.Size = new System.Drawing.Size(45, 15); + lblSamsungPresetDevice.TabIndex = 39; + lblSamsungPresetDevice.Text = "Device:"; + // + // btnAddLg + // + btnAddLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + btnAddLg.Location = new System.Drawing.Point(195, 284); + btnAddLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnAddLg.Name = "btnAddLg"; + btnAddLg.Size = new System.Drawing.Size(88, 27); + btnAddLg.TabIndex = 28; + btnAddLg.Text = "Add"; + btnAddLg.UseVisualStyleBackColor = true; + btnAddLg.Click += btnAddLg_Click; + // + // edtShortcutLg + // + edtShortcutLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + edtShortcutLg.Enabled = false; + edtShortcutLg.Location = new System.Drawing.Point(100, 411); + edtShortcutLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtShortcutLg.Name = "edtShortcutLg"; + edtShortcutLg.ReadOnly = true; + edtShortcutLg.Size = new System.Drawing.Size(233, 23); + edtShortcutLg.TabIndex = 11; + edtShortcutLg.TextChanged += edtShortcutLg_TextChanged; + edtShortcutLg.KeyDown += edtShortcut_KeyDown; + edtShortcutLg.KeyUp += edtShortcut_KeyUp; + // + // edtSamsungDeviceFilter + // + edtSamsungDeviceFilter.Location = new System.Drawing.Point(100, 40); + edtSamsungDeviceFilter.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtSamsungDeviceFilter.Name = "edtSamsungDeviceFilter"; + edtSamsungDeviceFilter.Size = new System.Drawing.Size(165, 23); + edtSamsungDeviceFilter.TabIndex = 23; + edtSamsungDeviceFilter.TextChanged += edtSamsungDeviceFilter_TextChanged; + // + // btnSamsungRemoveDevice + // + btnSamsungRemoveDevice.Location = new System.Drawing.Point(573, 8); + btnSamsungRemoveDevice.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungRemoveDevice.Name = "btnSamsungRemoveDevice"; + btnSamsungRemoveDevice.Size = new System.Drawing.Size(88, 27); + btnSamsungRemoveDevice.TabIndex = 38; + btnSamsungRemoveDevice.Text = "Remove"; + btnSamsungRemoveDevice.UseVisualStyleBackColor = true; + btnSamsungRemoveDevice.Click += btnSamsungRemoveDevice_Click; + // + // btnSamsungRefreshApps + // + btnSamsungRefreshApps.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + btnSamsungRefreshApps.Enabled = false; + btnSamsungRefreshApps.Location = new System.Drawing.Point(341, 378); + btnSamsungRefreshApps.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungRefreshApps.Name = "btnSamsungRefreshApps"; + btnSamsungRefreshApps.Size = new System.Drawing.Size(88, 27); + btnSamsungRefreshApps.TabIndex = 29; + btnSamsungRefreshApps.Text = "Refresh"; + btnSamsungRefreshApps.UseVisualStyleBackColor = true; + btnSamsungRefreshApps.Click += btnSamsungRefreshApps_Click; + // + // label2 + // + label2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + label2.AutoSize = true; + label2.Location = new System.Drawing.Point(6, 415); + label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label2.Name = "label2"; + label2.Size = new System.Drawing.Size(55, 15); + label2.TabIndex = 12; + label2.Text = "Shortcut:"; + // + // label1 + // + label1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(6, 321); + label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(42, 15); + label1.TabIndex = 20; + label1.Text = "Name:"; + // + // btnSamsungAddDevice + // + btnSamsungAddDevice.Location = new System.Drawing.Point(478, 8); + btnSamsungAddDevice.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungAddDevice.Name = "btnSamsungAddDevice"; + btnSamsungAddDevice.Size = new System.Drawing.Size(88, 27); + btnSamsungAddDevice.TabIndex = 37; + btnSamsungAddDevice.Text = "Add"; + btnSamsungAddDevice.UseVisualStyleBackColor = true; + btnSamsungAddDevice.Click += btnSamsungAddDevice_Click; + // + // edtNameLg + // + edtNameLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + edtNameLg.Enabled = false; + edtNameLg.Location = new System.Drawing.Point(100, 318); + edtNameLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtNameLg.Name = "edtNameLg"; + edtNameLg.Size = new System.Drawing.Size(233, 23); + edtNameLg.TabIndex = 19; + // + // btnSetShortcutLg + // + btnSetShortcutLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + btnSetShortcutLg.Enabled = false; + btnSetShortcutLg.Location = new System.Drawing.Point(384, 284); + btnSetShortcutLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSetShortcutLg.Name = "btnSetShortcutLg"; + btnSetShortcutLg.Size = new System.Drawing.Size(88, 27); + btnSetShortcutLg.TabIndex = 13; + btnSetShortcutLg.Text = "Save"; + btnSetShortcutLg.UseVisualStyleBackColor = true; + btnSetShortcutLg.Click += btnSetShortcutLg_Click; + // + // edtStepsLg + // + edtStepsLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + edtStepsLg.Enabled = false; + edtStepsLg.Location = new System.Drawing.Point(100, 440); + edtStepsLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + edtStepsLg.Multiline = true; + edtStepsLg.Name = "edtStepsLg"; + edtStepsLg.Size = new System.Drawing.Size(699, 43); + edtStepsLg.TabIndex = 17; + // + // btnSamsungAddButton + // + btnSamsungAddButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + btnSamsungAddButton.ContextMenuStrip = mnuLgButtons; + btnSamsungAddButton.Enabled = false; + btnSamsungAddButton.Location = new System.Drawing.Point(807, 439); + btnSamsungAddButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungAddButton.Name = "btnSamsungAddButton"; + btnSamsungAddButton.Size = new System.Drawing.Size(88, 27); + btnSamsungAddButton.TabIndex = 32; + btnSamsungAddButton.Text = "Add step"; + btnSamsungAddButton.UseVisualStyleBackColor = true; + btnSamsungAddButton.Click += btnSamsungAddButton_Click; + // + // mnuLgButtons + // + mnuLgButtons.ImageScalingSize = new System.Drawing.Size(20, 20); + mnuLgButtons.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuLgRcButtons, mnuLgActions, mnuLgNvPresets, mnuLgAmdPresets, mnuLgProgram }); + mnuLgButtons.Name = "mnuLgButtons"; + mnuLgButtons.Size = new System.Drawing.Size(153, 114); + mnuLgButtons.Opening += mnuLgButtons_Opening; + // + // mnuLgRcButtons + // + mnuLgRcButtons.Name = "mnuLgRcButtons"; + mnuLgRcButtons.Size = new System.Drawing.Size(152, 22); + mnuLgRcButtons.Text = "Buttons"; + // + // mnuLgActions + // + mnuLgActions.Name = "mnuLgActions"; + mnuLgActions.Size = new System.Drawing.Size(152, 22); + mnuLgActions.Text = "Actions"; + // + // mnuLgNvPresets + // + mnuLgNvPresets.Name = "mnuLgNvPresets"; + mnuLgNvPresets.Size = new System.Drawing.Size(152, 22); + mnuLgNvPresets.Text = "NVIDIA presets"; + // + // mnuLgAmdPresets + // + mnuLgAmdPresets.Name = "mnuLgAmdPresets"; + mnuLgAmdPresets.Size = new System.Drawing.Size(152, 22); + mnuLgAmdPresets.Text = "AMD presets"; + // + // mnuLgProgram + // + mnuLgProgram.Name = "mnuLgProgram"; + mnuLgProgram.Size = new System.Drawing.Size(152, 22); + mnuLgProgram.Text = "Start Program"; + mnuLgProgram.Click += mnuLgProgram_Click; + // + // btnCloneLg + // + btnCloneLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + btnCloneLg.Enabled = false; + btnCloneLg.Location = new System.Drawing.Point(100, 284); + btnCloneLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnCloneLg.Name = "btnCloneLg"; + btnCloneLg.Size = new System.Drawing.Size(88, 27); + btnCloneLg.TabIndex = 14; + btnCloneLg.Text = "Clone"; + btnCloneLg.UseVisualStyleBackColor = true; + btnCloneLg.Click += btnCloneLg_Click; + // + // lblStepsLg + // + lblStepsLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + lblStepsLg.AutoSize = true; + lblStepsLg.Location = new System.Drawing.Point(6, 445); + lblStepsLg.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblStepsLg.Name = "lblStepsLg"; + lblStepsLg.Size = new System.Drawing.Size(38, 15); + lblStepsLg.TabIndex = 16; + lblStepsLg.Text = "Steps:"; + // + // btnSamsungDeviceFilterRefresh + // + btnSamsungDeviceFilterRefresh.Location = new System.Drawing.Point(273, 39); + btnSamsungDeviceFilterRefresh.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnSamsungDeviceFilterRefresh.Name = "btnSamsungDeviceFilterRefresh"; + btnSamsungDeviceFilterRefresh.Size = new System.Drawing.Size(88, 27); + btnSamsungDeviceFilterRefresh.TabIndex = 35; + btnSamsungDeviceFilterRefresh.Text = "Refresh"; + btnSamsungDeviceFilterRefresh.UseVisualStyleBackColor = true; + btnSamsungDeviceFilterRefresh.Click += btnSamsungDeviceFilterRefresh_Click; + // + // clbLgPower + // + clbLgPower.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + clbLgPower.ColumnWidth = 240; + clbLgPower.FormattingEnabled = true; + clbLgPower.Items.AddRange(new object[] { "Power on after startup", "Power on after resume from standby", "Power off on shutdown", "Power off on standby", "Power off when screensaver activates", "Power on when screensaver deactivates", "Power on even after manual power off", "Allow triggers to be fired for this device", "Use Windows power settings to power on", "Use Windows power settings to power off", "Use secure connection" }); + clbLgPower.Location = new System.Drawing.Point(6, 70); + clbLgPower.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + clbLgPower.MultiColumn = true; + clbLgPower.Name = "clbLgPower"; + clbLgPower.Size = new System.Drawing.Size(861, 76); + clbLgPower.TabIndex = 34; + clbLgPower.ItemCheck += clbLgPower_ItemCheck; + clbLgPower.SelectedIndexChanged += clbLgPower_SelectedIndexChanged; + // + // lblSamsungError + // + lblSamsungError.AutoSize = true; + lblSamsungError.Location = new System.Drawing.Point(6, 9); + lblSamsungError.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + lblSamsungError.Name = "lblSamsungError"; + lblSamsungError.Size = new System.Drawing.Size(53, 15); + lblSamsungError.TabIndex = 15; + lblSamsungError.Text = "ErrorText"; + lblSamsungError.Visible = false; + // + // mnuLgSettings + // + mnuLgSettings.ImageScalingSize = new System.Drawing.Size(20, 20); + mnuLgSettings.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miTestPowerOffOn, miNvSettings }); + mnuLgSettings.Name = "mnuLgButtons"; + mnuLgSettings.Size = new System.Drawing.Size(168, 48); + // + // miTestPowerOffOn + // + miTestPowerOffOn.Name = "miTestPowerOffOn"; + miTestPowerOffOn.Size = new System.Drawing.Size(167, 22); + miTestPowerOffOn.Text = "Test power off/on"; + miTestPowerOffOn.Click += miTestPowerOffOn_Click; + // + // miNvSettings + // + miNvSettings.Name = "miNvSettings"; + miNvSettings.Size = new System.Drawing.Size(167, 22); + miNvSettings.Text = "Settings"; + miNvSettings.Click += miNvSettings_Click; + // + // SamsungPanel + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + Controls.Add(scSamsungController); + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + Name = "SamsungPanel"; + Size = new System.Drawing.Size(1114, 539); + scSamsungController.Panel1.ResumeLayout(false); + scSamsungController.Panel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)scSamsungController).EndInit(); + scSamsungController.ResumeLayout(false); + mnuLgButtons.ResumeLayout(false); + mnuLgSettings.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.ToolStripMenuItem mnuLgRcButtons; + private System.Windows.Forms.ToolStripMenuItem mnuLgActions; + private System.Windows.Forms.ContextMenuStrip mnuLgButtons; + private System.Windows.Forms.SplitContainer scSamsungController; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.ComboBox cbxSamsungDevices; + private System.Windows.Forms.Button btnSamsungDeviceConvertToCustom; + private System.Windows.Forms.ComboBox cbxSamsungApps; + private System.Windows.Forms.ListView lvSamsungPresets; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.ComboBox cbxSamsungPresetDevice; + private System.Windows.Forms.Button btnDeleteLg; + private System.Windows.Forms.Button btnApplyLg; + private System.Windows.Forms.Label lblDeviceFilter; + private System.Windows.Forms.Label lblSamsungPresetDevice; + private System.Windows.Forms.Button btnAddLg; + private System.Windows.Forms.TextBox edtShortcutLg; + private System.Windows.Forms.TextBox edtSamsungDeviceFilter; + private System.Windows.Forms.Button btnSamsungRemoveDevice; + private System.Windows.Forms.Button btnSamsungRefreshApps; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button btnSamsungAddDevice; + private System.Windows.Forms.TextBox edtNameLg; + private System.Windows.Forms.Button btnSetShortcutLg; + private System.Windows.Forms.TextBox edtStepsLg; + private System.Windows.Forms.Button btnSamsungAddButton; + private System.Windows.Forms.Button btnCloneLg; + private System.Windows.Forms.Label lblStepsLg; + private System.Windows.Forms.Button btnSamsungDeviceFilterRefresh; + private System.Windows.Forms.CheckedListBox clbLgPower; + private System.Windows.Forms.Label lblSamsungError; + private System.Windows.Forms.CheckBox chkSamsungRemoteControlShow; + private System.Windows.Forms.ToolStripMenuItem mnuLgNvPresets; + private System.Windows.Forms.ToolStripMenuItem mnuLgAmdPresets; + private System.Windows.Forms.ComboBox cbxSamsungPresetTrigger; + private System.Windows.Forms.Label lblSamsungPresetTrigger; + private System.Windows.Forms.Label lblSamsungPresetTriggerCondition; + private System.Windows.Forms.Label lblSamsungPresetExcludedProcesses; + private System.Windows.Forms.TextBox edtSamsungPresetExcludedProcesses; + private System.Windows.Forms.Label lblSamsungPresetIncludedProcesses; + private System.Windows.Forms.TextBox edtSamsungPresetIncludedProcesses; + private System.Windows.Forms.Button btnSamsungPresetEditTriggerConditions; + private System.Windows.Forms.TextBox edtSamsungPresetTriggerConditions; + private System.Windows.Forms.Button btnSamsungDeviceOptionsHelp; + private System.Windows.Forms.TextBox edtSamsungPresetDescription; + private System.Windows.Forms.Label lblSamsungPresetDescription; + private System.Windows.Forms.ComboBox cbxSamsungPcHdmiPort; + private System.Windows.Forms.Label lblSamsungPcHdmiPort; + private System.Windows.Forms.ToolStripMenuItem mnuLgProgram; + private System.Windows.Forms.CheckBox chkSamsungQuickAccess; + private System.Windows.Forms.Button btnSamsungSettings; + private System.Windows.Forms.ContextMenuStrip mnuLgSettings; + private System.Windows.Forms.ToolStripMenuItem miTestPowerOffOn; + private System.Windows.Forms.ToolStripMenuItem miNvSettings; + private System.Windows.Forms.Button btnSamsungDeviceOptionsSettings; + private System.Windows.Forms.ToolStripMenuItem mnuLgSetSvcMenuFlag; + private System.Windows.Forms.ToolStripMenuItem miLgFullServiceMenuEnable; + private System.Windows.Forms.ToolStripMenuItem miLgFullServiceMenuDisable; + } +} diff --git a/ColorControl/Services/Samsung/SamsungPanel.cs b/ColorControl/Services/Samsung/SamsungPanel.cs new file mode 100644 index 0000000..06618c6 --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungPanel.cs @@ -0,0 +1,1113 @@ +using ColorControl.Common; +using ColorControl.Forms; +using ColorControl.Native; +using ColorControl.Services.AMD; +using ColorControl.Services.Common; +using ColorControl.Services.NVIDIA; +using NLog; +using NStandard; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ColorControl.Services.Samsung +{ + public partial class SamsungPanel : UserControl, IModulePanel + { + public static readonly int SHORTCUTID_SAMSUNGQA = -204; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private Config _config; + private SamsungService _samsungService; + private NvService _nvService; + private AmdService _amdService; + private IntPtr _mainHandle; + + private string _lgTabMessage; + private bool _disableEvents = false; + + internal SamsungPanel(SamsungService samsungService, NvService nvService, AmdService amdService, IntPtr handle) + { + _samsungService = samsungService; + _nvService = nvService; + _amdService = amdService; + _mainHandle = handle; + + _config = AppContext.CurrentContext.Config; + + InitializeComponent(); + + FillLgPresets(); + + FormUtils.InitSortState(lvSamsungPresets, _config.LgPresetsSortState); + + //_samsungService.AfterApplyPreset += SamsungServiceAfterApplyPreset; + //_samsungService.SelectedDeviceChangedEvent += _samsungService_SelectedDeviceChangedEvent; + + edtSamsungDeviceFilter.Text = _samsungService.Config.DeviceSearchKey; + + var values = Enum.GetValues(typeof(ButtonType)); + foreach (var button in values) + { + var text = button.ToString(); + if (text[0] == '_') + { + text = text.Substring(1); + } + + var item = mnuLgRcButtons.DropDownItems.AddCustom(text); + item.Click += miLgAddButton_Click; + } + } + + public void Init() + { + var _ = Handle; + _samsungService.RefreshDevices(afterStartUp: true).ContinueWith((_) => BeginInvoke(() => AfterSamsungServiceRefreshDevices())); + _samsungService.InstallEventHandlers(); + } + + private void _samsungService_SelectedDeviceChangedEvent(object sender, EventArgs e) + { + BeginInvoke(() => SetSamsungDevicesSelectedIndex(sender)); + } + + private void SetSamsungDevicesSelectedIndex(object sender) + { + if (sender == null) + { + cbxSamsungDevices.SelectedIndex = -1; + return; + } + + cbxSamsungDevices.SelectedIndex = cbxSamsungDevices.Items.IndexOf(sender); + } + + private void AfterSamsungServiceRefreshDevices() + { + FillSamsungDevices(); + + var startUpParams = AppContext.CurrentContext.StartUpParams; + + if (startUpParams.ExecuteLgPreset) + { + var _ = _samsungService.ApplyPreset(startUpParams.LgPresetName); + } + } + + internal void Save() + { + FormUtils.SaveSortState(lvSamsungPresets.ListViewItemSorter, _config.LgPresetsSortState); + } + + private void edtShortcut_KeyDown(object sender, KeyEventArgs e) + { + ((TextBox)sender).Text = Utils.FormatKeyboardShortcut(e); + } + + private void edtShortcut_KeyUp(object sender, KeyEventArgs e) + { + Utils.HandleKeyboardShortcutUp(e); + } + + private void FillLgPresets() + { + FormUtils.InitListView(lvSamsungPresets, SamsungPreset.GetColumnNames()); + + foreach (var preset in _samsungService.GetPresets()) + { + AddOrUpdateItemLg(preset); + Utils.RegisterShortcut(_mainHandle, preset.id, preset.shortcut); + } + } + + private void AddOrUpdateItemLg(SamsungPreset preset = null, ListViewItem specItem = null) + { + FormUtils.AddOrUpdateListItem(lvSamsungPresets, _samsungService.GetPresets(), _config, preset, specItem); + } + + private void btnCloneLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); + + var newPreset = preset.Clone(); + AddOrUpdateItemLg(newPreset); + } + + private SamsungPreset GetSelectedLgPreset() + { + if (lvSamsungPresets.SelectedItems.Count > 0) + { + var item = lvSamsungPresets.SelectedItems[0]; + return (SamsungPreset)item.Tag; + } + else + { + return null; + } + } + + private void lvSamsungPresets_SelectedIndexChanged(object sender, EventArgs e) + { + var enabled = lvSamsungPresets.SelectedItems.Count > 0; + btnApplyLg.Enabled = enabled; + btnCloneLg.Enabled = enabled; + edtNameLg.Enabled = enabled; + chkSamsungQuickAccess.Enabled = enabled; + edtSamsungPresetDescription.Enabled = enabled; + cbxSamsungPresetDevice.Enabled = enabled; + edtShortcutLg.Enabled = enabled; + btnSetShortcutLg.Enabled = enabled; + edtStepsLg.Enabled = enabled; + btnSamsungAddButton.Enabled = enabled; + cbxSamsungApps.Enabled = enabled; + btnSamsungRefreshApps.Enabled = enabled; + btnDeleteLg.Enabled = enabled; + cbxSamsungPresetTrigger.Enabled = enabled; + edtSamsungPresetTriggerConditions.Enabled = enabled; + btnSamsungPresetEditTriggerConditions.Enabled = enabled; + edtSamsungPresetIncludedProcesses.Enabled = enabled; + edtSamsungPresetExcludedProcesses.Enabled = enabled; + + var preset = GetSelectedLgPreset(); + + if (preset != null) + { + edtNameLg.Text = preset.name; + chkSamsungQuickAccess.Checked = preset.ShowInQuickAccess; + edtSamsungPresetDescription.Text = preset.Description; + var lgApps = _samsungService?.GetApps(); + cbxSamsungApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.AppId.Equals(preset.AppId)); + edtShortcutLg.Text = preset.shortcut; + edtStepsLg.Text = preset.Steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b); + + var index = -1; + + for (var i = 0; i < cbxSamsungPresetDevice.Items.Count; i++) + { + var pnpDev = (SamsungDevice)cbxSamsungPresetDevice.Items[i]; + if ((string.IsNullOrEmpty(preset.DeviceMacAddress) && string.IsNullOrEmpty(pnpDev.MacAddress)) || pnpDev.MacAddress.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase)) + { + index = i; + break; + } + } + + cbxSamsungPresetDevice.SelectedIndex = index; + + var trigger = preset.Triggers.FirstOrDefault(); + + FormUtils.SetComboBoxEnumIndex(cbxSamsungPresetTrigger, (int)(trigger?.Trigger ?? PresetTriggerType.None)); + + edtSamsungPresetTriggerConditions.Text = Utils.GetDescriptions(trigger != null ? (int)trigger.Conditions : 0).Join(", "); + edtSamsungPresetTriggerConditions.Tag = trigger != null ? (int)trigger.Conditions : 0; + + edtSamsungPresetIncludedProcesses.Text = trigger?.IncludedProcesses?.Join(", ") ?? string.Empty; + edtSamsungPresetExcludedProcesses.Text = trigger?.ExcludedProcesses?.Join(", ") ?? string.Empty; + } + else + { + edtNameLg.Text = string.Empty; + chkSamsungQuickAccess.Checked = false; + edtSamsungPresetDescription.Text = string.Empty; + cbxSamsungApps.SelectedIndex = -1; + edtShortcutLg.Text = string.Empty; + edtStepsLg.Text = string.Empty; + cbxSamsungPresetDevice.SelectedIndex = -1; + cbxSamsungPresetTrigger.SelectedIndex = -1; + edtSamsungPresetTriggerConditions.Text = string.Empty; + edtSamsungPresetTriggerConditions.Tag = 0; + edtSamsungPresetIncludedProcesses.Text = string.Empty; + edtSamsungPresetExcludedProcesses.Text = string.Empty; + } + } + + private async void btnApplyLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); + + await ApplyPreset(preset); + } + + private void btnSetShortcutLg_Click(object sender, EventArgs e) + { + var shortcut = edtShortcutLg.Text.Trim(); + if (!Utils.ValidateShortcut(shortcut)) + { + return; + } + + var preset = GetSelectedLgPreset(); + + var name = edtNameLg.Text.Trim(); + + if (name.Length == 0 || _samsungService.GetPresets().Any(x => x.id != preset.id && x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + MessageForms.WarningOk("The name can not be empty and must be unique."); + return; + } + + var clear = !string.IsNullOrEmpty(preset.shortcut); + + preset.name = name; + preset.ShowInQuickAccess = chkSamsungQuickAccess.Checked; + preset.Description = edtSamsungPresetDescription.Text.Trim(); + + if (cbxSamsungPresetDevice.SelectedIndex == -1) + { + preset.DeviceMacAddress = string.Empty; + } + else + { + var device = (SamsungDevice)cbxSamsungPresetDevice.SelectedItem; + preset.DeviceMacAddress = device.MacAddress; + } + + if (cbxSamsungApps.SelectedIndex == -1) + { + preset.AppId = string.Empty; + } + else + { + var lgApp = (SamsungApp)cbxSamsungApps.SelectedItem; + preset.AppId = lgApp.AppId; + } + + var triggerType = FormUtils.GetComboBoxEnumItem(cbxSamsungPresetTrigger); + preset.UpdateTrigger(triggerType, + (PresetConditionType)edtSamsungPresetTriggerConditions.Tag, + edtSamsungPresetIncludedProcesses.Text, + edtSamsungPresetExcludedProcesses.Text); + + var shortcutChanged = !shortcut.Equals(preset.shortcut); + if (shortcutChanged) + { + preset.shortcut = shortcut; + } + + var text = edtStepsLg.Text; + + Utils.ParseWords(preset.Steps, text); + + AddOrUpdateItemLg(); + + if (shortcutChanged) + { + Utils.RegisterShortcut(_mainHandle, preset.id, preset.shortcut, clear); + } + } + + public void UpdateInfo() + { + if (_samsungService == null) + { + return; + } + + //if (scSamsungController.Panel2.Controls.Count == 0) + //{ + // var rcPanel = new RemoteControlPanel(_samsungService, _samsungService.GetRemoteControlButtons()); + // rcPanel.Parent = scSamsungController.Panel2; + // rcPanel.Dock = DockStyle.Fill; + + // if (DarkModeUtils.UseDarkMode) + // { + // DarkModeUtils.SetControlTheme(rcPanel); + // } + //} + //chkSamsungRemoteControlShow.Checked = _samsungService.Config.ShowRemoteControl; + //scSamsungController.Panel2Collapsed = !_samsungService.Config.ShowRemoteControl; + + FormUtils.BuildComboBox(cbxSamsungPresetTrigger, PresetTriggerType.Resume, PresetTriggerType.Shutdown, PresetTriggerType.Standby, PresetTriggerType.Startup, PresetTriggerType.Reserved5, PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop); + + if (!string.IsNullOrEmpty(_lgTabMessage)) + { + MessageForms.WarningOk(_lgTabMessage); + _lgTabMessage = null; + } + } + + private void FillSamsungDevices() + { + var devices = _samsungService.Devices; + + cbxSamsungDevices.Items.Clear(); + cbxSamsungDevices.Items.AddRange(devices.ToArray()); + + cbxSamsungPresetDevice.Items.Clear(); + cbxSamsungPresetDevice.Items.AddRange(devices.ToArray()); + var globalDevice = new SamsungDevice("Globally selected device", string.Empty, string.Empty, true, true); + cbxSamsungPresetDevice.Items.Insert(0, globalDevice); + + var device = _samsungService.SelectedDevice; + + if (device != null) + { + cbxSamsungDevices.SelectedIndex = cbxSamsungDevices.Items.IndexOf(device); + } + + if (!devices.Any()) + { + var message = "It seems there's no LG TV available! Please make sure it's connected to the same network as this PC."; + + if (Visible /*tcMain.SelectedTab == tabLG*/) + { + MessageForms.WarningOk(message); + } + else + { + _lgTabMessage = message; + } + } + + if (cbxSamsungApps.Items.Count == 0 && device != null) + { + //_samsungService.RefreshApps().ContinueWith((task) => BeginInvoke(() => FillLgApps(false))); + } + + btnSamsungDeviceConvertToCustom.Enabled = devices.Any(); + + cbxSamsungDevices_SelectedIndexChanged(cbxSamsungDevices, null); + + SetSamsungDevicePowerOptions(); + } + + private void SetSamsungDevicePowerOptions() + { + var device = _samsungService.SelectedDevice; + clbLgPower.Enabled = device != null; + + _disableEvents = true; + try + { + clbLgPower.SetItemChecked(0, device?.Options.PowerOnAfterStartup ?? false); + clbLgPower.SetItemChecked(1, device?.Options.PowerOnAfterResume ?? false); + clbLgPower.SetItemChecked(2, device?.Options.PowerOffOnShutdown ?? false); + clbLgPower.SetItemChecked(3, device?.Options.PowerOffOnStandby ?? false); + clbLgPower.SetItemChecked(4, device?.Options.PowerOffOnScreenSaver ?? false); + clbLgPower.SetItemChecked(5, device?.Options.PowerOnAfterScreenSaver ?? false); + clbLgPower.SetItemChecked(6, device?.Options.PowerOnAfterManualPowerOff ?? false); + clbLgPower.SetItemChecked(7, device?.Options.TriggersEnabled ?? true); + clbLgPower.SetItemChecked(8, device?.Options.PowerOffByWindows ?? false); + clbLgPower.SetItemChecked(9, device?.Options.PowerOnByWindows ?? false); + clbLgPower.SetItemChecked(10, device?.Options.UseSecureConnection ?? false); + } + finally + { + _disableEvents = false; + } + } + + private void FillLgApps(bool forced) + { + var lgApps = _samsungService?.GetApps(); + if (forced && (lgApps == null || !lgApps.Any())) + { + MessageForms.WarningOk("Could not refresh the apps. Check the log for details."); + return; + } + InitLgApps(); + } + + private void InitLgApps() + { + var lgApps = _samsungService?.GetApps(); + + cbxSamsungApps.Items.Clear(); + if (lgApps != null) + { + cbxSamsungApps.Items.AddRange(lgApps.ToArray()); + } + + for (var i = 0; i < lvSamsungPresets.Items.Count; i++) + { + var item = lvSamsungPresets.Items[i]; + AddOrUpdateItemLg((SamsungPreset)item.Tag, item); + } + + var preset = GetSelectedLgPreset(); + if (preset != null) + { + cbxSamsungApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.AppId.Equals(preset.AppId)); + } + } + + private void btnDeleteLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); + + if (preset == null) + { + return; + } + + if (!string.IsNullOrEmpty(preset.shortcut)) + { + WinApi.UnregisterHotKey(_mainHandle, preset.id); + } + + _samsungService.GetPresets().Remove(preset); + + var item = lvSamsungPresets.SelectedItems[0]; + lvSamsungPresets.Items.Remove(item); + } + + private void btnAddLg_Click(object sender, EventArgs e) + { + AddOrUpdateItemLg(_samsungService.CreateNewPreset()); + } + + internal async Task ApplyPreset(SamsungPreset preset) + { + if (preset == null) + { + return; + } + + var result = await _samsungService.ApplyPreset(preset); + if (!result) + { + MessageForms.WarningOk("Could not apply the preset (entirely). Check the log for details."); + } + } + + private void btnSamsungRefreshApps_Click(object sender, EventArgs e) + { + //_samsungService.RefreshApps(true).ContinueWith((task) => BeginInvoke(() => FillLgApps(true))); + } + + private void cbxSamsungDevices_SelectedIndexChanged(object sender, EventArgs e) + { + cbxSamsungPcHdmiPort.Enabled = cbxSamsungDevices.SelectedIndex > -1; + if (cbxSamsungDevices.SelectedIndex == -1) + { + _samsungService.SelectedDevice = null; + cbxSamsungPcHdmiPort.SelectedIndex = 0; + } + else + { + _samsungService.SelectedDevice = (SamsungDevice)cbxSamsungDevices.SelectedItem; + //cbxSamsungPcHdmiPort.SelectedIndex = _samsungService.SelectedDevice.HDMIPortNumber; + } + chkSamsungRemoteControlShow.Enabled = _samsungService.SelectedDevice != null; + btnSamsungRemoveDevice.Enabled = _samsungService.SelectedDevice != null && _samsungService.SelectedDevice.IsCustom; + btnSamsungDeviceConvertToCustom.Enabled = _samsungService.SelectedDevice != null && !_samsungService.SelectedDevice.IsCustom; + + SetSamsungDevicePowerOptions(); + } + + private void lvSamsungPresets_DoubleClick(object sender, EventArgs e) + { + ApplySelectedLgPreset(); + } + + private async void ApplySelectedLgPreset() + { + var preset = GetSelectedLgPreset(); + await ApplyPreset(preset); + } + + private void btnSamsungAddButton_Click(object sender, EventArgs e) + { + mnuLgButtons.ShowCustom(btnSamsungAddButton); + } + + private void miLgAddButton_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + + FormUtils.AddStepToTextBox(edtStepsLg, item.Text); + } + + private void miLgAddAction_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var action = item.Tag as SamsungDevice.InvokableAction; + + var value = ShowLgActionForm(action); + + var text = action.Name; + + if (!string.IsNullOrEmpty(value)) + { + text += $"({value})"; + } + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private string ShowLgActionForm(SamsungDevice.InvokableAction action) + { + var text = action.Name; + var title = action.Title ?? action.Name; + + var value = string.Empty; + + if (action.EnumType == null) + { + if (action.MinValue != action.MaxValue) + { + List fields = new(); + + if (action.NumberOfValues == 1) + { + fields.Add(new MessageForms.FieldDefinition + { + Label = "Enter desired " + title, + FieldType = MessageForms.FieldType.Numeric, + MinValue = action.MinValue, + MaxValue = action.MaxValue, + }); + } + else + { + var array = Enumerable.Range(0, action.NumberOfValues); + + fields.AddRange(array.Select(i => new MessageForms.FieldDefinition + { + Label = "Value for " + (action.ValueLabels != null ? action.ValueLabels[i] : i.ToString()), + FieldType = MessageForms.FieldType.Numeric, + MinValue = action.MinValue, + MaxValue = action.MaxValue, + })); + } + + var values = MessageForms.ShowDialog($"Enter value for {title}", fields); + + if (!values.Any()) + { + return null; + } + + if (values.Count > 1) + { + value = string.Join("; ", values.Select(v => v.Value)); + } + else + { + value = values.First().Value.ToString(); + } + } + } + else + { + var dropDownValues = new List(); + foreach (var enumValue in Enum.GetValues(action.EnumType)) + { + var description = Utils.GetDescription(action.EnumType, enumValue as IConvertible) ?? enumValue.ToString().Replace("_", ""); + dropDownValues.Add(description); + } + + var values = MessageForms.ShowDialog("Choose value", new[] { + new MessageForms.FieldDefinition + { + Label = "Choose desired " + title, + FieldType = MessageForms.FieldType.DropDown, + Values = dropDownValues + } + }); + + if (!values.Any()) + { + return null; + } + + value = values.First().Value.ToString(); + var enumName = Utils.GetEnumNameByDescription(action.EnumType, value); + if (enumName != null) + { + value = enumName; + } + } + + return value; + } + + private void miLgAddNvPreset_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var preset = item.Tag as NvPreset; + + var text = $"NvPreset({preset.name})"; + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private void miLgAddAmdPreset_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var preset = item.Tag as AmdPreset; + + var text = $"AmdPreset({preset.name})"; + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e) + { + if (_disableEvents) + { + return; + } + + var device = _samsungService?.SelectedDevice; + if (device == null) + { + return; + } + + if (e.Index is 0 or 1 or 4 or 7 && !(device.Options.PowerOnAfterResume || device.Options.PowerOnAfterStartup || device.Options.PowerOnAfterScreenSaver || device.Options.PowerOnByWindows)) + { + MessageForms.InfoOk( +@"Be sure to activate the following setting on the TV, or the app will not be able to wake the TV: + +Connection > Network > Expert Settings: +1) Power On with Mobile +2) IP Remote + +Use 'Settings > Test power off/on' to test this functionality." + ); + } + + BeginInvoke(() => + { + device.Options.PowerOnAfterStartup = clbLgPower.GetItemChecked(0); + device.Options.PowerOnAfterResume = clbLgPower.GetItemChecked(1); + device.Options.PowerOffOnShutdown = clbLgPower.GetItemChecked(2); + device.Options.PowerOffOnStandby = clbLgPower.GetItemChecked(3); + device.Options.PowerOffOnScreenSaver = clbLgPower.GetItemChecked(4); + device.Options.PowerOnAfterScreenSaver = clbLgPower.GetItemChecked(5); + device.Options.PowerOnAfterManualPowerOff = clbLgPower.GetItemChecked(6); + device.Options.TriggersEnabled = clbLgPower.GetItemChecked(7); + device.Options.PowerOffByWindows = clbLgPower.GetItemChecked(8); + device.Options.PowerOnByWindows = clbLgPower.GetItemChecked(9); + device.Options.UseSecureConnection = clbLgPower.GetItemChecked(10); + + if (e.Index == 10) + { + _samsungService.RefreshDevices().ContinueWith((_) => BeginInvoke(() => FillSamsungDevices())); + } + }); + } + + private void edtSamsungDeviceFilter_TextChanged(object sender, EventArgs e) + { + if (_samsungService != null) + { + _samsungService.Config.DeviceSearchKey = edtSamsungDeviceFilter.Text; + } + } + + private void btnSamsungDeviceFilterRefresh_Click(object sender, EventArgs e) + { + RefreshSamsungDevices(); + } + + private void RefreshSamsungDevices() + { + _samsungService.RefreshDevices(false).ContinueWith((task) => BeginInvoke(() => FillSamsungDevices())); + } + + private void edtShortcutLg_TextChanged(object sender, EventArgs e) + { + var text = edtShortcutLg.Text; + + var preset = GetSelectedLgPreset(); + + FormUtils.UpdateShortcutTextBox(edtShortcutLg, preset); + } + + private async void btnSamsungAddDevice_Click(object sender, EventArgs e) + { + var values = MessageForms.ShowDialog("Add tv", new[] { "Name", "Ip-address", "MAC-address" }, ValidateAddDevice); + if (values.Any()) + { + var device = new SamsungDevice(values[0].Value.ToString(), values[1].Value.ToString(), values[2].Value.ToString()); + + var form = MessageForms.ShowProgress("Connecting to device..."); + + var result = false; + Enabled = false; + try + { + result = await device.TestConnectionAsync(); + } + finally + { + form.Close(); + Enabled = true; + } + + if (!result && MessageForms.QuestionYesNo("Unable to connect to the device. Are you sure you want to add it?") != DialogResult.Yes) + { + return; + } + + _samsungService.AddCustomDevice(device); + + FillSamsungDevices(); + } + } + + private string ValidateAddDevice(IEnumerable values) + { + if (values.Any(v => string.IsNullOrEmpty(v.Value?.ToString()))) + { + return "Please fill in all the fields"; + } + + return null; + } + + private void btnSamsungRemoveDevice_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo("Are you sure you want to remove this device?") != DialogResult.Yes) + { + return; + } + + var device = _samsungService.SelectedDevice; + + if (device != null) + { + _samsungService.RemoveCustomDevice(device); + + FillSamsungDevices(); + } + } + + private void mnuLgButtons_Opening(object sender, CancelEventArgs e) + { + mnuLgActions.DropDownItems.Clear(); + + var preset = GetSelectedLgPreset(); + + if (preset == null) + { + return; + } + + var device = _samsungService.GetPresetDevice(preset); + + BuildLgActionMenu(device, mnuLgActions.DropDownItems, mnuLgActions.Name, miLgAddAction_Click); + FormUtils.BuildServicePresetsMenu(mnuLgNvPresets, _nvService, "NVIDIA", miLgAddNvPreset_Click); + FormUtils.BuildServicePresetsMenu(mnuLgAmdPresets, _amdService, "AMD", miLgAddAmdPreset_Click); + } + + private void btnSamsungDeviceConvertToCustom_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo( +@"This will convert the automatically detected device to a custom variant. +This means that the device will remain here even if it is not detected anymore. +Do you want to continue?" + ) != DialogResult.Yes) + { + return; + } + + _samsungService.SelectedDevice.ConvertToCustom(); + + FillSamsungDevices(); + } + + private void chkSamsungRemoteControlShow_CheckedChanged(object sender, EventArgs e) + { + scSamsungController.Panel2Collapsed = !chkSamsungRemoteControlShow.Checked; + //_samsungService.Config.ShowRemoteControl = chkSamsungRemoteControlShow.Checked; + } + + private void BuildLgActionMenu(SamsungDevice device, ToolStripItemCollection parent, string parentName, EventHandler clickEvent, bool showAdvanced = false, bool showGameBar = false) + { + if (device == null) + { + return; + } + + var actions = new List();// device.GetInvokableActions(showAdvanced); + var gameBarActions = new List();// device.GetInvokableActionsForGameBar(); + var activatedGameBarActions = new List();// device.GetActionsForGameBar(); + + var expertActions = actions.Where(a => a.EnumType != null || a.MaxValue > a.MinValue || a.AsyncFunction != null).ToList(); + + var categories = expertActions.Select(a => a.Category ?? "misc").Where(c => !string.IsNullOrEmpty(c)).Distinct(); + + foreach (var category in categories) + { + var catMenuItem = FormUtils.BuildDropDownMenuEx(parent, parentName, Utils.FirstCharUpperCase(category), null, null, category); + + foreach (var action in expertActions.Where(a => (a.Category ?? "misc") == category)) + { + if (!showGameBar) + { + var text = action.Title ?? action.Name; + + var item = catMenuItem.DropDownItems.AddCustom(text); + item.Tag = action; + item.Click += clickEvent; + continue; + } + + var menu = FormUtils.BuildDropDownMenuEx(catMenuItem.DropDownItems, catMenuItem.Name, action.Title, action.EnumType, clickEvent, action, (int)action.MinValue, (int)action.MaxValue, action.NumberOfValues > 1); + + if (!gameBarActions.Contains(action)) + { + continue; + } + } + } + + if (!showAdvanced) + { + return; + } + } + + private void cbxSamsungApps_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == ((char)Keys.Back)) + { + cbxSamsungApps.SelectedIndex = -1; + } + } + + private void btnSamsungPresetEditTriggerConditions_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 = edtSamsungPresetTriggerConditions.Tag ?? 0 + } + }); + + if (!values.Any()) + { + return; + } + + var value = (PresetConditionType)values.First().Value; + edtSamsungPresetTriggerConditions.Tag = (PresetConditionType)values.First().Value; + edtSamsungPresetTriggerConditions.Text = Utils.GetDescriptions((int)value).Join(", "); + } + + private void edtSamsungGameBarShortcut_KeyDown(object sender, KeyEventArgs e) + { + ((TextBox)sender).Text = Utils.FormatKeyboardShortcut(e); + } + + private void edtSamsungGameBarShortcut_KeyUp(object sender, KeyEventArgs e) + { + Utils.HandleKeyboardShortcutUp(e); + } + + private void btnSamsungDeviceOptionsHelp_Click(object sender, EventArgs e) + { + MessageForms.InfoOk( +@"Notes: +- power on after startup requires ""Automatically start after login"" - see Options +- power on after resume from standby may need some retries for waking TV - see Options +- power off on shutdown: because this app cannot detect a restart, restarting could also trigger this. Hold down Ctrl on restart to prevent power off. +- Use Windows power settings: powering on/off of TV will follow Windows settings. If activated, it's better to disable all other power options except power off on shutdown. +"); + } + + private void cbxSamsungPcHdmiPort_SelectedIndexChanged(object sender, EventArgs e) + { + if (_samsungService?.SelectedDevice == null) + { + return; + } + + //_samsungService.SelectedDevice.HDMIPortNumber = cbxSamsungPcHdmiPort.SelectedIndex; + } + + private void lvSamsungPresets_ColumnClick(object sender, ColumnClickEventArgs e) + { + FormUtils.ListViewSort(sender, e); + } + + private void mnuLgProgram_Click(object sender, EventArgs e) + { + var file = Utils.SelectFile(); + + if (file != null) + { + FormUtils.AddStepToTextBox(edtStepsLg, $"StartProgram({file.FullName})"); + } + } + + private void btnSamsungSettings_Click(object sender, EventArgs e) + { + mnuLgSettings.ShowCustom(btnSamsungSettings); + } + + private void lvSamsungPresets_ItemChecked(object sender, ItemCheckedEventArgs e) + { + FormUtils.ListViewItemChecked(lvSamsungPresets, e); + } + + private async void miTestPowerOffOn_Click(object sender, EventArgs e) + { + var device = _samsungService.SelectedDevice; + + if (device == null) + { + return; + } + + var text = +@"The TV will now power off. Please wait for the TV to be powered off completely (relay click) and press ENTER to wake it again. +For waking up to work, you need to activate the following setting on the TV: + +Connection > Network > Expert Settings: +1) Power On with Mobile +2) IP Remote + +It will also work over a wired connection. + +Do you want to continue?"; + + if (MessageForms.QuestionYesNo(text) == DialogResult.Yes) + { + await device.ConnectAsync(); + + await device.PowerOffAsync(); + + MessageForms.InfoOk("Press ENTER to wake the TV."); + + await device.WakeAndConnect(); + } + } + + private void miNvSettings_Click(object sender, EventArgs e) + { + var powerRetries = new MessageForms.FieldDefinition + { + FieldType = MessageForms.FieldType.Numeric, + Label = "Maximum number of retries powering on after startup/resume.", + SubLabel = "Retries are necessary to wait for the network link of your pc to be established.", + MinValue = 1, + MaxValue = 40, + Value = _samsungService.Config.PowerOnRetries + }; + + var shutDownDelayField = new MessageForms.FieldDefinition + { + FieldType = MessageForms.FieldType.Numeric, + Label = "Delay when shutting down/restarting PC (milliseconds).", + SubLabel = "This delay may prevent the tv from powering off when restarting the pc.", + MinValue = 0, + MaxValue = 5000, + Value = _samsungService.Config.ShutdownDelay + }; + + var buttonDelayField = new MessageForms.FieldDefinition + { + FieldType = MessageForms.FieldType.Numeric, + Label = "Default delay between remote control button presses (milliseconds).", + SubLabel = "Increasing this delay may prevent skipped button presses.", + MinValue = 200, + MaxValue = 1000, + Value = _samsungService.Config.DefaultButtonDelay + }; + + var quickAccessShortcutField = new MessageForms.FieldDefinition + { + FieldType = MessageForms.FieldType.Shortcut, + Label = "Quick Access shortcut", + Value = _samsungService.Config.QuickAccessShortcut + }; + + var setDeviceField = new MessageForms.FieldDefinition + { + FieldType = MessageForms.FieldType.CheckBox, + Label = "Automatically set selected device to last powered on", + Value = _samsungService.Config.SetSelectedDeviceByPowerOn + }; + + + var values = MessageForms.ShowDialog("Samsung controller settings", new[] { powerRetries, shutDownDelayField, buttonDelayField, quickAccessShortcutField, setDeviceField }); + + if (values?.Any() != true) + { + return; + } + + _samsungService.Config.PowerOnRetries = powerRetries.ValueAsInt; + _samsungService.Config.ShutdownDelay = shutDownDelayField.ValueAsInt; + _samsungService.Config.DefaultButtonDelay = buttonDelayField.ValueAsInt; + + var shortcutQA = quickAccessShortcutField.Value.ToString(); + var clear = !string.IsNullOrEmpty(_samsungService.Config.QuickAccessShortcut); + + _samsungService.Config.QuickAccessShortcut = shortcutQA; + + Utils.RegisterShortcut(_mainHandle, SHORTCUTID_SAMSUNGQA, _samsungService.Config.QuickAccessShortcut, clear); + + _samsungService.Config.SetSelectedDeviceByPowerOn = setDeviceField.ValueAsBool; + } + + private void clbLgPower_SelectedIndexChanged(object sender, EventArgs e) + { + btnSamsungDeviceOptionsSettings.Enabled = clbLgPower.SelectedIndex == 4; + } + + private void btnSamsungDeviceOptionsSettings_Click(object sender, EventArgs e) + { + var device = _samsungService.SelectedDevice; + var selectedItem = clbLgPower.SelectedItem; + + var durationField = new MessageForms.FieldDefinition + { + Label = "Enter the minimal duration the screen saver must be running (in seconds)", + FieldType = MessageForms.FieldType.Numeric, + MinValue = 0, + MaxValue = 3600, + Value = device.Options.ScreenSaverMinimalDuration + }; + var turnOffField = new MessageForms.FieldDefinition + { + Label = "Turn screen off instead of power off", + FieldType = MessageForms.FieldType.CheckBox, + Value = device.Options.TurnScreenOffOnScreenSaver + }; + var turnOnField = new MessageForms.FieldDefinition + { + Label = "Turn screen on instead of power on", + FieldType = MessageForms.FieldType.CheckBox, + Value = device.Options.TurnScreenOnAfterScreenSaver + }; + var manualField = new MessageForms.FieldDefinition + { + Label = "Perform action even on manually executed screen saver", + FieldType = MessageForms.FieldType.CheckBox, + Value = device.Options.HandleManualScreenSaver + }; + + if (!MessageForms.ShowDialog($"Settings - {selectedItem}", new[] { durationField, turnOffField, turnOnField, manualField }).Any()) + { + return; + } + + device.Options.ScreenSaverMinimalDuration = durationField.ValueAsInt; + device.Options.TurnScreenOffOnScreenSaver = turnOffField.ValueAsBool; + device.Options.TurnScreenOnAfterScreenSaver = turnOnField.ValueAsBool; + device.Options.HandleManualScreenSaver = manualField.ValueAsBool; + } + } +} diff --git a/ColorControl/Services/Samsung/SamsungPanel.resx b/ColorControl/Services/Samsung/SamsungPanel.resx new file mode 100644 index 0000000..877bf37 --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungPanel.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 288, 16 + + + 17, 17 + + + 147, 17 + + \ No newline at end of file diff --git a/ColorControl/Services/Samsung/SamsungPreset.cs b/ColorControl/Services/Samsung/SamsungPreset.cs new file mode 100644 index 0000000..331ddb3 --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungPreset.cs @@ -0,0 +1,98 @@ +using ColorControl.Services.Common; +using System.Collections.Generic; +using System.Linq; + +namespace ColorControl.Services.Samsung +{ + class SamsungPreset : PresetBase + { + public string AppId { get; set; } + public List Steps { get; set; } + public string DeviceMacAddress { get; set; } + public static List SamsungDevices { get; internal set; } + + public SamsungPreset() : base() + { + Steps = new List(); + } + + public SamsungPreset(SamsungPreset preset) : this() + { + id = GetNewId(); + + name = preset.name; + AppId = preset.AppId; + DeviceMacAddress = preset.DeviceMacAddress; + Steps.AddRange(preset.Steps); + } + + public SamsungPreset Clone() + { + var preset = new SamsungPreset(this); + + preset.name += " (copy)"; + + return preset; + } + + public static string[] GetColumnNames() + { + return new[] { "Name", "Device|120", "App|200", "Steps|400", "Shortcut", "Trigger" }; + } + + public override List GetDisplayValues(Config config = null) + { + var values = new List(); + + values.Add(name); + + var deviceString = "Global"; + if (!string.IsNullOrEmpty(DeviceMacAddress)) + { + if (SamsungDevices != null) + { + var device = SamsungDevices.FirstOrDefault(d => !string.IsNullOrEmpty(d.MacAddress) && d.MacAddress.Equals(DeviceMacAddress)); + if (device != null) + { + deviceString = device.Name; + } + else + { + deviceString = "Unknown: device not found"; + } + } + else + { + deviceString = DeviceMacAddress; + } + } + values.Add(deviceString); + + var app = AppId; + //if (LgApps != null) + //{ + // var item = LgApps.FirstOrDefault(x => x.appId.Equals(appId)); + // if (item != null) + // { + // app = item.title + " (" + appId + ")"; + // } + //} + values.Add(app); + values.Add(GetStepsDisplay()); + values.Add(shortcut); + + if (Triggers.Any()) + { + values.Add(Triggers.First().ToString()); + } + + return values; + } + + public string GetStepsDisplay() + { + return Steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b); + } + } +} + diff --git a/ColorControl/Services/Samsung/SamsungService.cs b/ColorControl/Services/Samsung/SamsungService.cs new file mode 100644 index 0000000..d7b3532 --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungService.cs @@ -0,0 +1,676 @@ +using ColorControl.Common; +using ColorControl.Services.Common; +using ColorControl.Services.EventDispatcher; +using ColorControl.Services.LG; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using NWin32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ColorControl.Services.Samsung +{ + class SamsungService : ServiceBase + { + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + public override string ServiceName => "Samsung"; + + protected override string PresetsBaseFilename => "SamsungPresets.json"; + + public SamsungServiceConfig Config { get; private set; } + public SamsungDevice SelectedDevice { get; set; } + + private bool _allowPowerOn; + private ServiceManager _serviceManager; + private readonly PowerEventDispatcher _powerEventDispatcher; + private readonly SessionSwitchDispatcher _sessionSwitchDispatcher; + private readonly RestartDetector _restartDetector; + private readonly ProcessEventDispatcher _processEventDispatcher; + private SamTvConnection _samTvConnection; + private string _configFilename; + private bool _poweredOffByScreenSaver; + private int _poweredOffByScreenSaverProcessId; + private object _lastTriggeredPreset; + + public List Devices { get; private set; } + + public SamsungService(AppContextProvider appContextProvider, ServiceManager serviceManager, PowerEventDispatcher powerEventDispatcher, SessionSwitchDispatcher sessionSwitchDispatcher, RestartDetector restartDetector, ProcessEventDispatcher processEventDispatcher) : base(appContextProvider) + { + _allowPowerOn = appContextProvider.GetAppContext().StartUpParams.RunningFromScheduledTask; + _serviceManager = serviceManager; + _powerEventDispatcher = powerEventDispatcher; + _sessionSwitchDispatcher = sessionSwitchDispatcher; + _restartDetector = restartDetector; + _processEventDispatcher = processEventDispatcher; + SamTvConnection.SyncContext = _appContextProvider.GetAppContext().SynchronizationContext; + + LoadConfig(); + LoadPresets(); + } + + public static async Task ExecutePresetAsync(string presetName) + { + try + { + var samsungService = Program.ServiceProvider.GetRequiredService(); + + await samsungService.RefreshDevices(afterStartUp: true); + + var result = await samsungService.ApplyPreset(presetName); + + if (!result) + { + Console.WriteLine("Preset not found or error while executing."); + } + + return result; + } + catch (Exception ex) + { + Console.WriteLine("Error executing preset: " + ex.ToLogString()); + return false; + } + } + + public void Init() + { + var _ = RefreshDevices(afterStartUp: true); + InstallEventHandlers(); + } + + public void InstallEventHandlers() + { + _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOn, PowerModeChanged); + _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOff, PowerModeChanged); + _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Suspend, PowerModeChanged); + _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Resume, PowerModeChanged); + _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Shutdown, PowerModeChanged); + _processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged); + } + + private void LoadConfig() + { + _configFilename = Path.Combine(_dataPath, "SamsungConfig.json"); + try + { + if (File.Exists(_configFilename)) + { + Config = JsonConvert.DeserializeObject(File.ReadAllText(_configFilename)); + } + } + catch (Exception ex) + { + Logger.Error($"LoadConfig: {ex.Message}"); + } + Config ??= new SamsungServiceConfig(); + + //if (!Config.Devices.Any()) + //{ + // var device = new SamsungDevice("S95C", "192.168.178.40", "BC-45-5B-A4-70-A4", true); + // device.Options.PowerOffOnShutdown = true; + // device.Options.PowerOffOnStandby = true; + // device.Options.PowerOnAfterResume = true; + // device.Options.PowerOnAfterStartup = true; + + // Config.Devices.Add(device); + //} + + SamsungPreset.SamsungDevices = Config.Devices; + } + + private void PowerModeChanged(object sender, PowerStateChangedEventArgs e) + { + Logger.Debug($"PowerModeChanged: {e.State}"); + + switch (e.State) + { + case PowerOnOffState.Resume: + { + WakeAfterStartupOrResume(PowerOnOffState.Resume); + break; + } + case PowerOnOffState.StandBy: + { + var devices = Devices.Where(d => d.Options.PowerOffOnStandby); + PowerOffDevices(devices, PowerOnOffState.StandBy); + break; + } + case PowerOnOffState.ShutDown: + { + var devices = Devices.Where(d => d.Options.PowerOffOnShutdown); + + PowerOffDevices(devices); + break; + } + + case PowerOnOffState.MonitorOff: + { + var devices = Devices.Where(d => d.Options.PowerOffByWindows).ToList(); + PowerOffDevices(devices, PowerOnOffState.MonitorOff); + break; + } + case PowerOnOffState.MonitorOn: + { + var devices = Devices.Where(d => d.Options.PowerOnByWindows).ToList(); + PowerOnDevicesTask(devices); + break; + } + } + } + + public async Task RefreshDevices(bool connect = true, bool afterStartUp = false) + { + Devices = Config.Devices; + var customIpAddresses = Devices.Where(d => d.IsCustom).Select(d => d.IpAddress); + + var pnpDevices = await Utils.GetPnpDevices(Config.DeviceSearchKey); + + var autoDevices = pnpDevices.Where(p => !customIpAddresses.Contains(p.IpAddress)).Select(d => new SamsungDevice(d.Name, d.IpAddress, d.MacAddress, false)).ToList(); + var autoIpAddresses = pnpDevices.Select(d => d.IpAddress); + + Devices.RemoveAll(d => !d.IsCustom && !autoIpAddresses.Contains(d.IpAddress)); + + var newAutoDevices = autoDevices.Where(ad => ad.IpAddress != null && !Devices.Any(d => d.IpAddress != null && d.IpAddress.Equals(ad.IpAddress))); + Devices.AddRange(newAutoDevices); + + if (Devices.Any()) + { + var preferredDevice = Devices.FirstOrDefault(x => x.MacAddress != null && x.MacAddress.Equals(Config.PreferredMacAddress)) ?? Devices[0]; + + SelectedDevice = preferredDevice; + } + else + { + SelectedDevice = null; + } + + if (afterStartUp && SelectedDevice == null && Config.PowerOnAfterStartup && !string.IsNullOrEmpty(Config.PreferredMacAddress)) + { + Logger.Debug("No device has been found, trying to wake it first..."); + + var tempDevice = new LgDevice("Test", string.Empty, Config.PreferredMacAddress); + tempDevice.Wake(); + + await Task.Delay(4000); + await RefreshDevices(); + + return; + } + + if (connect && SelectedDevice != null) + { + if (_allowPowerOn) + { + WakeAfterStartupOrResume(); + } + else + { + var _ = SelectedDevice.ConnectAsync(); + } + } + } + + protected override List GetDefaultPresets() + { + return new List(); + } + + private void SavePresets() + { + Utils.WriteObject(_presetsFilename, _presets); + } + + private void SaveConfig() + { + Config.PowerOnAfterStartup = Devices?.Any(d => d.Options.PowerOnAfterStartup) ?? false; + + Utils.WriteObject(_configFilename, Config); + } + + public void GlobalSave() + { + SavePresets(); + SaveConfig(); + } + + public SamsungDevice GetPresetDevice(SamsungPreset preset) + { + var device = string.IsNullOrEmpty(preset.DeviceMacAddress) + ? SelectedDevice + : Devices.FirstOrDefault(d => d.MacAddress?.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase) ?? false); + return device; + } + + public async Task ApplyPreset(string idOrName) + { + var preset = GetPresetByIdOrName(idOrName); + if (preset != null) + { + return await ApplyPreset(preset, Program.AppContext); + } + else + { + return false; + } + } + + public override async Task ApplyPreset(SamsungPreset preset) + { + return await ApplyPreset(preset, Program.AppContext); + } + + public async Task ApplyPreset(SamsungPreset preset, AppContext appContext) + { + var result = true; + + var device = GetPresetDevice(preset); + + await device?.ExecutePresetAsync(preset, appContext, Config); + + _lastAppliedPreset = preset; + + PresetApplied(); + + return result; + } + + internal void AddCustomDevice(SamsungDevice device) + { + Devices.Add(device); + } + + internal void RemoveCustomDevice(SamsungDevice device) + { + Devices.Remove(device); + + if (!string.IsNullOrEmpty(device.MacAddress)) + { + var presets = _presets.Where(p => !string.IsNullOrEmpty(p.DeviceMacAddress) && p.DeviceMacAddress.Equals(device.MacAddress, StringComparison.OrdinalIgnoreCase)).ToList(); + presets.ForEach(preset => + { + preset.DeviceMacAddress = null; + }); + } + + if (SelectedDevice == device) + { + SelectedDevice = Devices.Any() ? Devices.First() : null; + } + } + + internal void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + { + //Devices.ForEach(d => d.ClearPowerOffTask()); + + var wakeDevices = Devices.Where(d => state == PowerOnOffState.StartUp && d.Options.PowerOnAfterStartup || + state == PowerOnOffState.Resume && d.Options.PowerOnAfterResume || + state == PowerOnOffState.ScreenSaver && d.Options.PowerOnAfterScreenSaver); + + PowerOnDevicesTask(wakeDevices, state, checkUserSession); + } + + public void PowerOffDevices(IEnumerable devices, PowerOnOffState state = PowerOnOffState.ShutDown) + { + if (!devices.Any()) + { + return; + } + + if (NativeMethods.GetAsyncKeyState(NativeConstants.VK_CONTROL) < 0 || NativeMethods.GetAsyncKeyState(NativeConstants.VK_RCONTROL) < 0) + { + Logger.Debug("Not powering off because CONTROL-key is down"); + return; + } + + var error = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED); + try + { + Logger.Debug($"SetThreadExecutionState: {error}, Thread#: {Environment.CurrentManagedThreadId}"); + if (state == PowerOnOffState.ShutDown) + { + var sleep = Config.ShutdownDelay; + + Logger.Debug($"PowerOffDevices: Waiting for a maximum of {sleep} milliseconds..."); + + while (sleep > 0 && _restartDetector?.PowerOffDetected == false) + { + Thread.Sleep(100); + + if (_restartDetector != null && (_restartDetector.RestartDetected || _restartDetector.IsRebootInProgress())) + { + Logger.Debug("Not powering off because of a restart"); + return; + } + + sleep -= 100; + } + } + + Logger.Debug("Powering off tv(s)..."); + var tasks = new List(); + foreach (var device in devices) + { + var task = device.PowerOffAsync(); + + tasks.Add(task); + } + + if (state == PowerOnOffState.StandBy) + { + var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); + if (File.Exists(standByScript)) + { + Utils.StartProcess(standByScript, hidden: true, wait: true); + } + } + + // We can't use async here because we need to stay on the main thread... + var _ = Task.WhenAll(tasks.ToArray()).ConfigureAwait(true); + + Logger.Debug("Done powering off tv(s)"); + } + finally + { + var error2 = NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS); + Logger.Debug($"SetThreadExecutionState (reset): {error2}, Thread#: {Environment.CurrentManagedThreadId}"); + } + } + + public void PowerOnDevicesTask(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + { + Task.Run(async () => await PowerOnDevices(devices, state, checkUserSession)); + } + + public async Task PowerOnDevices(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + { + if (!devices.Any()) + { + return; + } + + if (checkUserSession && !_sessionSwitchDispatcher.UserLocalSession) + { + Logger.Debug($"WakeAfterStartupOrResume: not waking because session info indicates no local session"); + return; + } + + var resumeScript = Path.Combine(Program.DataDir, "ResumeScript.bat"); + + var tasks = new List(); + + foreach (var device in devices) + { + if (!device.Options.PowerOnAfterManualPowerOff && device.PoweredOffBy == PowerOffSource.External) + { + Logger.Debug($"[{device.Name}]: device was manually powered off by user, not powering on"); + continue; + } + + //if (state == PowerOnOffState.ScreenSaver) + //{ + // tasks.Add(device.PerformActionAfterScreenSaver(Config.PowerOnRetries)); + // continue; + //} + + var task = device.WakeAndConnectWithRetries(Config.PowerOnRetries); + tasks.Add(task); + } + + await Task.WhenAll(tasks.ToArray()); + + if (File.Exists(resumeScript)) + { + Utils.StartProcess(resumeScript, hidden: true); + } + } + + internal List GetApps() + { + return new List(); + } + + + public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, CancellationToken token) + { + try + { + var wasConnected = Devices.Any(d => d.IsConnected()); + + var applicableDevices = Devices.Where(d => d.Options.PowerOffOnScreenSaver || d.Options.PowerOnAfterScreenSaver || d.Options.TriggersEnabled && _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None) && + ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))); + + if (!applicableDevices.Any()) + { + return; + } + + if (_poweredOffByScreenSaver && !_sessionSwitchDispatcher.UserLocalSession) + { + return; + } + + var processes = args.RunningProcesses; + + if (_poweredOffByScreenSaver) + { + if (processes.Any(p => p.Id == _poweredOffByScreenSaverProcessId)) + { + return; + } + + var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr")); + + if (process != null) + { + Logger.Debug($"Detected screen saver process switch from {_poweredOffByScreenSaverProcessId} to {process.Id}"); + + _poweredOffByScreenSaverProcessId = process.Id; + return; + } + + Logger.Debug("Screensaver stopped, waking"); + _poweredOffByScreenSaver = false; + _poweredOffByScreenSaverProcessId = 0; + + WakeAfterStartupOrResume(PowerOnOffState.ScreenSaver); + + return; + } + + var connectedDevices = applicableDevices.Where(d => d.IsConnected()).ToList(); + + if (!connectedDevices.Any()) + { + if (wasConnected) + { + Logger.Debug("Process monitor: TV(s) where connected, but not any longer"); + wasConnected = false; + + return; + } + else + { + var devices = applicableDevices.ToList(); + foreach (var device in devices) + { + //Logger.Debug($"Process monitor: test connection with {device.Name}..."); + + var test = await device.TestConnectionAsync(); + //Logger.Debug("Process monitor: test connection result: " + test); + } + + connectedDevices = applicableDevices.Where(d => d.IsConnected()).ToList(); + + if (!connectedDevices.Any()) + { + return; + } + } + } + + if (!wasConnected) + { + Logger.Debug("Process monitor: TV(s) where not connected, but connection has now been established"); + wasConnected = true; + } + + if (connectedDevices.Any(d => d.Options.PowerOffOnScreenSaver || d.Options.PowerOnAfterScreenSaver)) + { + await HandleScreenSaverProcessAsync(processes, connectedDevices); + } + + await ExecuteProcessPresets(args, connectedDevices); + + } + catch (Exception ex) + { + Logger.Error("ProcessChanged: " + ex.ToLogString()); + } + } + + private async Task ExecuteProcessPresets(ProcessChangedEventArgs context, IList connectedDevices) + { + var triggerDevices = connectedDevices.Where(d => d.Options.TriggersEnabled).ToList(); + + if (context.IsScreenSaverActive) + { + return; + } + + var selectedDevice = SelectedDevice; + + var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch) && + ((string.IsNullOrEmpty(p.DeviceMacAddress) && selectedDevice?.Options.TriggersEnabled == true) + || Devices.Any(d => d.Options.TriggersEnabled && p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))); + if (!presets.Any()) + { + return; + } + + var changedProcesses = new List(); + if (context.ForegroundProcess != null) + { + changedProcesses.Add(context.ForegroundProcess); + } + + if (context.ForegroundProcess != null && context.ForegroundProcessIsFullScreen) + { + presets = presets.Where(p => p.Triggers.Any(t => t.Conditions.HasFlag(PresetConditionType.FullScreen))); + } + else if (context.StoppedFullScreen) + { + presets = presets.Where(p => p.Triggers.Any(t => !t.Conditions.HasFlag(PresetConditionType.FullScreen))); + } + + var isHDRActive = CCD.IsHDREnabled(); + + var isGsyncActive = await _serviceManager.HandleExternalServiceAsync("GsyncEnabled", new[] { "" }); + + var triggerContext = new PresetTriggerContext + { + IsHDRActive = isHDRActive, + IsGsyncActive = isGsyncActive, + ForegroundProcess = context.ForegroundProcess, + ForegroundProcessIsFullScreen = context.ForegroundProcessIsFullScreen, + ChangedProcesses = changedProcesses, + IsNotificationDisabled = context.IsNotificationDisabled + }; + + var toApplyPresets = presets.Where(p => p.Triggers.Any(t => t.TriggerActive(triggerContext))).ToList(); + + if (toApplyPresets.Any()) + { + var toApplyPreset = toApplyPresets.FirstOrDefault(p => toApplyPresets.Count == 1 || p.Triggers.Any(t => !t.IncludedProcesses.Contains("*"))) ?? toApplyPresets.First(); + + if (_lastTriggeredPreset != toApplyPreset) + { + Logger.Debug($"Applying triggered preset: {toApplyPreset.name} because trigger conditions are met"); + + await ApplyPreset(toApplyPreset); + + _lastTriggeredPreset = toApplyPreset; + } + } + } + + private async Task HandleScreenSaverProcessAsync(IList processes, List connectedDevices) + { + var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr")); + + var powerOffDevices = connectedDevices.Where(d => d.Options.PowerOffOnScreenSaver).ToList(); + + if (process == null) + { + powerOffDevices.ForEach(d => d.ClearPowerOffTask()); + + return; + } + + var parent = process.Parent(processes); + + // Ignore manually started screen savers if no devices prefer this + if (parent?.ProcessName.Contains("winlogon") != true && !connectedDevices.Any(d => d.Options.HandleManualScreenSaver)) + { + return; + } + + Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}"); + try + { + foreach (var device in powerOffDevices) + { + Logger.Debug($"Screensaver check: test connection with {device.Name}..."); + + var test = await device.TestConnectionAsync(); + Logger.Debug("Screensaver check: test connection result: " + test); + if (!test) + { + continue; + } + + if (device.Options.ScreenSaverMinimalDuration > 0) + { + Logger.Debug($"Screensaver check: powering off tv {device.Name} in {device.Options.ScreenSaverMinimalDuration} seconds because of screensaver"); + + _poweredOffByScreenSaver = true; + + device.PowerOffIn(device.Options.ScreenSaverMinimalDuration); + + continue; + } + + Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver"); + try + { + _poweredOffByScreenSaver = await device.PerformActionOnScreenSaver().WaitAsync(TimeSpan.FromSeconds(5)); + } + catch (TimeoutException) + { + // Assume powering off was successful + _poweredOffByScreenSaver = true; + Logger.Error($"Screensaver check: timeout exception while powering off (probably connection closed)"); + } + 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}"); + } + + _poweredOffByScreenSaverProcessId = process.Id; + } + catch (Exception e) + { + Logger.Error($"Screensaver check: can't power off: " + e.ToLogString()); + } + } + } +} diff --git a/ColorControl/Services/Samsung/SamsungServiceConfig.cs b/ColorControl/Services/Samsung/SamsungServiceConfig.cs new file mode 100644 index 0000000..af3de5f --- /dev/null +++ b/ColorControl/Services/Samsung/SamsungServiceConfig.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace ColorControl.Services.Samsung +{ + class SamsungServiceConfig + { + public bool PowerOnAfterStartup { get; set; } + public int PowerOnRetries { get; set; } + public string PreferredMacAddress { get; set; } + public string DeviceSearchKey { get; set; } + public List Devices { get; set; } + public int ShutdownDelay { get; set; } + public bool SetSelectedDeviceByPowerOn { get; set; } + public string QuickAccessShortcut { get; set; } + public int DefaultButtonDelay { get; set; } + + public SamsungServiceConfig() + { + ShutdownDelay = 1000; + DeviceSearchKey = "Samsung"; + PowerOnRetries = 10; + DefaultButtonDelay = 500; + Devices = new List(); + } + } +} diff --git a/ColorControl/StartUpParams.cs b/ColorControl/StartUpParams.cs index 3d03ecf..4ebe0f7 100644 --- a/ColorControl/StartUpParams.cs +++ b/ColorControl/StartUpParams.cs @@ -13,6 +13,7 @@ namespace ColorControl public const string ExecuteNvidiaPresetParam = "--nvpreset"; public const string ExecuteAmdPresetParam = "--amdpreset"; public const string ExecuteLgPresetParam = "--lgpreset"; + public const string ExecuteSamsungPresetParam = "--sampreset"; public const string NoGuiParam = "--nogui"; public const string EnableAutoStartParam = "--enable-auto-start"; public const string DisableAutoStartParam = "--disable-auto-start"; @@ -36,6 +37,8 @@ namespace ColorControl public string AmdPresetIdOrName { get; private set; } public bool ExecuteLgPreset { get; private set; } public string LgPresetName { get; private set; } + public bool ExecuteSamsungPreset { get; private set; } + public string SamsungPresetName { get; private set; } public bool NoGui { get; set; } public bool EnableAutoStart { get; private set; } public TaskRunLevel AutoStartRunLevel { get; private set; } = TaskRunLevel.LUA; @@ -85,6 +88,11 @@ namespace ColorControl settings.LgPresetName = arg; break; } + case ExecuteSamsungPresetParam: + { + settings.SamsungPresetName = arg; + break; + } case SetProcessAffinityParam: { if (settings.ProcessId == 0) @@ -172,6 +180,12 @@ namespace ColorControl parseNameParam = ExecuteLgPresetParam; break; } + case ExecuteSamsungPresetParam: + { + settings.ExecuteSamsungPreset = true; + parseNameParam = ExecuteSamsungPresetParam; + break; + } case NoGuiParam: { settings.NoGui = true; diff --git a/ColorControl/lgtv/IsConnectedDelegate.cs b/ColorControl/lgtv/IsConnectedDelegate.cs deleted file mode 100644 index de1e679..0000000 --- a/ColorControl/lgtv/IsConnectedDelegate.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace LgTv -{ - public delegate void IsConnectedDelegate(bool status); -} \ No newline at end of file diff --git a/ColorControl/lgtv/LgTvConnection.cs b/ColorControl/lgtv/LgTvConnection.cs index 8dae3ea..134f3f9 100644 --- a/ColorControl/lgtv/LgTvConnection.cs +++ b/ColorControl/lgtv/LgTvConnection.cs @@ -20,6 +20,8 @@ namespace LgTv } } + public delegate void IsConnectedDelegate(bool status); + public class LgTvApiCore : IDisposable { public static SynchronizationContext SyncContext { get; set; } @@ -111,17 +113,6 @@ namespace LgTv _messageWriter.WriteString(message); await _messageWriter.StoreAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(5)); - - //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) { @@ -197,11 +188,6 @@ namespace LgTv task.Start(); var dataReader = await task.WaitAsync(TimeSpan.FromSeconds(5)); - //if (!result) - //{ - // throw new Exception("Timeout while reading response, possible disconnect"); - //} - using (dataReader) { dataReader.UnicodeEncoding = UnicodeEncoding.Utf8;