From 66e682ba3de5694607693ad04df8fc6590b27b6e Mon Sep 17 00:00:00 2001 From: Vincent Maas Date: Sun, 3 Apr 2022 14:58:14 +0200 Subject: [PATCH] fixed corrupt json file issues, retrieving apps and added HDMI port selection and new power setting --- ColorControl/ColorControl.csproj | 4 +- ColorControl/LgDevice.cs | 53 +++-- ColorControl/LgService.cs | 363 ++++++++++++++++++------------ ColorControl/MainForm.Designer.cs | 142 +++++++----- ColorControl/MainForm.cs | 121 +++++----- ColorControl/Program.cs | 14 +- ColorControl/Utils.cs | 16 ++ ColorControl/lgtv/LgTvApi.cs | 7 + 8 files changed, 452 insertions(+), 268 deletions(-) diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index 4a89af4..2e95144 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -18,8 +18,8 @@ Maassoft Maassoft 0 - 5.6.0.0 - 5.6.0.0 + 5.7.0.0 + 5.7.0.0 false true false diff --git a/ColorControl/LgDevice.cs b/ColorControl/LgDevice.cs index e54b283..915cce2 100644 --- a/ColorControl/LgDevice.cs +++ b/ColorControl/LgDevice.cs @@ -53,7 +53,7 @@ namespace ColorControl public static Func ExternalServiceHandler; public static List DefaultActionsOnGameBar = new() { "backlight", "contrast", "brightness", "color" }; - public string Name { get; private set; } + public string Name { get; private set; } public string IpAddress { get; private set; } public string MacAddress { get; private set; } public bool IsCustom { get; private set; } @@ -66,7 +66,9 @@ namespace ColorControl public bool PowerOffOnStandby { get; set; } public bool PowerSwitchOnScreenSaver { get; set; } public bool PowerOnAfterManualPowerOff { get; set; } + public bool PowerByWindows { get; set; } public bool TriggersEnabled { get; set; } + public int HDMIPortNumber { get; set; } private List _actionsForGameBar; @@ -113,6 +115,9 @@ namespace ColorControl [JsonIgnore] public LgDevicePictureSettings PictureSettings { get; private set; } + [JsonIgnore] + public string CurrentAppId { get; private set; } + public event EventHandler PictureSettingsChangedEvent; public event EventHandler PowerStateChangedEvent; @@ -284,6 +289,7 @@ namespace ColorControl //await _lgTvApi.SubscribeVolume(VolumeChanged); await _lgTvApi.SubscribePowerState(PowerStateChanged); await _lgTvApi.SubscribePictureSettings(PictureSettingsChanged); + await _lgTvApi.SubscribeForegroundApp(ForegroundAppChanged); //await _lgTvApi.Reboot(); @@ -323,7 +329,7 @@ namespace ColorControl { var settings = payload.settings; - Logger.Debug($"PictureSettingsChanged: {JsonConvert.SerializeObject(settings)}"); + Logger.Debug($"[{Name}] PictureSettingsChanged: {JsonConvert.SerializeObject(settings)}"); if (settings.backlight != null) { @@ -385,6 +391,18 @@ namespace ColorControl return true; } + public bool ForegroundAppChanged(dynamic payload) + { + Logger.Debug($"[{Name}] ForegroundAppChanged: {JsonConvert.SerializeObject(payload)}"); + + if (payload.appId != null) + { + CurrentAppId = payload.appId; + } + + return true; + } + public bool IsConnected() { return !_lgTvApi?.ConnectionClosed ?? false; @@ -586,6 +604,11 @@ namespace ColorControl public async Task> GetApps(bool force = false) { + if (!force) + { + await Task.Delay(5000); + } + if (!await Connected(force)) { Logger.Debug("Cannot refresh apps: no connection could be made"); @@ -595,17 +618,30 @@ namespace ColorControl return await _lgTvApi.GetApps(force); } - internal async Task PowerOff() + internal async Task PowerOff(bool checkHdmi = false) { if (!await Connected(true) || CurrentState != PowerState.Active) { return false; } + if (checkHdmi && CurrentAppId != null && HDMIPortNumber != 0 && !CurrentAppId.EndsWith($".hdmi{HDMIPortNumber}", StringComparison.InvariantCulture)) + { + Logger.Debug($"[{Name}] PowerOff is ignored because current app {CurrentAppId} does not match configured HDMI port {HDMIPortNumber}"); + return true; + } + PoweredOffViaApp = true; _poweredOffViaAppDateTime = DateTimeOffset.Now; - await _lgTvApi.TurnOff(); + try + { + await _lgTvApi.TurnOff().WaitAsync(TimeSpan.FromSeconds(2)); + } + catch (TimeoutException ex) + { + Logger.Debug($"Timeout when turning off tv: {ex.Message}"); + } return true; } @@ -702,15 +738,6 @@ namespace ColorControl } } - if (result) - { - var resumeScript = Path.Combine(Program.DataDir, "ResumeScript.bat"); - if (File.Exists(resumeScript)) - { - Utils.StartProcess(resumeScript, hidden: true); - } - } - return result; } diff --git a/ColorControl/LgService.cs b/ColorControl/LgService.cs index 17a881c..fbd49ac 100644 --- a/ColorControl/LgService.cs +++ b/ColorControl/LgService.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -23,6 +24,13 @@ namespace ColorControl StandBy = 5 } + internal enum WindowsPowerSetting + { + Off = 0, + On = 1, + Dimmed = 2 + } + internal class ProcessMonitorContext { public List Devices { get; set; } @@ -66,6 +74,7 @@ namespace ColorControl private Task _monitorTask; private int _monitorTaskCounter; private LgPreset _lastTriggeredPreset; + private RestartDetector _restartDetector; public LgService(string dataPath, bool allowPowerOn) : base(dataPath, "LgPresets.json") { @@ -73,6 +82,8 @@ namespace ColorControl LgPreset.LgApps = _lgApps; + _restartDetector = new RestartDetector(); + LoadConfig(); LoadPresets(); LoadRemoteControlButtons(); @@ -140,21 +151,25 @@ namespace ColorControl presets = JsonConvert.DeserializeObject>(json); } - + return presets; } private void LoadConfig() { _configFilename = Path.Combine(_dataPath, "LgConfig.json"); - if (File.Exists(_configFilename)) + try { - Config = JsonConvert.DeserializeObject(File.ReadAllText(_configFilename)); + if (File.Exists(_configFilename)) + { + Config = JsonConvert.DeserializeObject(File.ReadAllText(_configFilename)); + } } - else + catch (Exception ex) { - Config = new LgServiceConfig(); + Logger.Error($"LoadConfig: {ex.Message}"); } + Config ??= new LgServiceConfig(); LgPreset.LgDevices = Config.Devices; } @@ -289,7 +304,7 @@ namespace ColorControl Devices.RemoveAll(d => !d.IsCustom && !autoIpAddresses.Contains(d.IpAddress)); - var newAutoDevices = autoDevices.Where(ad => !Devices.Any(d => d.IpAddress.Equals(ad.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()) @@ -404,22 +419,6 @@ namespace ColorControl return await SelectedDevice?.PowerOff(); } - internal Task PowerOffOnShutdownOrResume(PowerOnOffState state = PowerOnOffState.ShutDown) - { - var devices = Devices.Where(d => state == PowerOnOffState.ShutDown && d.PowerOffOnShutdown || - state == PowerOnOffState.StandBy && d.PowerOffOnStandby); - - var tasks = new List(); - foreach (var device in devices) - { - var task = device.PowerOff(); - - tasks.Add(task); - } - - return Task.WhenAll(tasks.ToArray()); - } - internal bool WakeSelectedDevice() { return SelectedDevice?.Wake() ?? false; @@ -427,26 +426,11 @@ namespace ColorControl internal void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) { - if (checkUserSession && !UserSessionInfo.UserLocalSession) - { - Logger.Debug($"WakeAfterStartupOrResume: not waking because session info indicates no local session"); - return; - } - - var wakeDevices = Devices.Where(d => state == PowerOnOffState.StartUp && d.PowerOnAfterStartup || - state == PowerOnOffState.Resume && d.PowerOnAfterResume || + var wakeDevices = Devices.Where(d => state == PowerOnOffState.StartUp && d.PowerOnAfterStartup || + state == PowerOnOffState.Resume && d.PowerOnAfterResume || state == PowerOnOffState.ScreenSaver && d.PowerSwitchOnScreenSaver); - foreach (var device in wakeDevices) - { - if (!device.PowerOnAfterManualPowerOff && device.PoweredOffBy == LgDevice.PowerOffSource.Manually) - { - Logger.Debug($"[{device.Name}]: device was manually powered off by user, not powering on"); - continue; - } - device.DisposeConnection(); - var _ = device.WakeAndConnectWithRetries(Config.PowerOnRetries); - } + PowerOnDevices(wakeDevices, state, checkUserSession); } public void InstallEventHandlers() @@ -492,27 +476,8 @@ namespace ColorControl return; } - if (Devices.Any(d => d.PowerOffOnStandby)) - { - NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED); - try - { - var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); - if (File.Exists(standByScript)) - { - Utils.StartProcess(standByScript, hidden: true); - } - - Logger.Debug("Powering off tv..."); - var task = PowerOffOnShutdownOrResume(PowerOnOffState.StandBy); - Utils.WaitForTask(task); - Logger.Debug("Done powering off tv"); - } - finally - { - NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS); - } - } + var devices = Devices.Where(d => d.PowerOffOnStandby); + PowerOffDevices(devices, PowerOnOffState.StandBy); } private void SessionEnded(object sender, SessionEndedEventArgs e) @@ -644,84 +609,15 @@ namespace ColorControl wasConnected = true; } + if (connectedDevices.Any(d => d.PowerSwitchOnScreenSaver)) + { + lastProcessId = await HandleScreenSaverProcessAsync(lastProcessId, processes, connectedDevices); + } + MonitorContext.Devices = connectedDevices.Where(d => d.TriggersEnabled).ToList(); MonitorContext.RunningProcesses = processes; - //if (lastProcesses != null) - //{ - // monitorContext.StartedProcesses = processes.Where(p => !lastProcesses.Any(lp => lp.Id == p.Id)).ToArray(); - // monitorContext.StoppedProcesses = lastProcesses.Where(lp => !processes.Any(p => p.Id == lp.Id)).ToArray(); - //} - //else - //{ - // monitorContext.StartedProcesses = null; - // monitorContext.StoppedProcesses = null; - //} - //if (monitorContext.StartedProcesses?.Any() ?? false) - //{ - // Logger.Debug("Started processes: " + string.Join(", ", monitorContext.StartedProcesses.Select(p => p.ProcessName))); - //} - //lastProcesses = processes; - - var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr")); - - if (process != null) - { - var parent = process.Parent(processes); - - if (parent?.ProcessName.Contains("winlogon") ?? false) - { - Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}"); - try - { - foreach (var device in connectedDevices) - { - Logger.Debug($"Screensaver check: test connection with {device.Name}..."); - var test = await device.TestConnection(); - Logger.Debug("Screensaver check: test connection result: " + test); - if (!test) - { - continue; - } - - Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver"); - try - { - _poweredOffByScreenSaver = await device.PowerOff().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; - lastProcessId = 0; - } - catch (Exception e) - { - Logger.Error($"Screensaver check: can't power off: " + e.ToLogString()); - } - } - else if (process.Id != lastProcessId) - { - Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}"); - lastProcessId = process.Id; - } - } - else - { - await ExecuteProcessPresets(MonitorContext); - } + await ExecuteProcessPresets(MonitorContext); } catch (Exception ex) { @@ -730,12 +626,75 @@ namespace ColorControl } } + private async Task HandleScreenSaverProcessAsync(int lastProcessId, Process[] processes, List connectedDevices) + { + var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr")); + + if (process == null) + { + return 0; + } + + var parent = process.Parent(processes); + + if (parent?.ProcessName.Contains("winlogon") ?? false) + { + Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}"); + try + { + foreach (var device in connectedDevices) + { + Logger.Debug($"Screensaver check: test connection with {device.Name}..."); + var test = await device.TestConnection(); + Logger.Debug("Screensaver check: test connection result: " + test); + if (!test) + { + continue; + } + + Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver"); + try + { + _poweredOffByScreenSaver = await device.PowerOff().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; + lastProcessId = 0; + } + catch (Exception e) + { + Logger.Error($"Screensaver check: can't power off: " + e.ToLogString()); + } + } + else if (process.Id != lastProcessId) + { + Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}"); + lastProcessId = process.Id; + } + + return lastProcessId; + } + private async Task ExecuteProcessPresets(ProcessMonitorContext context) { var selectedDevice = SelectedDevice; - var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch) && - ((string.IsNullOrEmpty(p.DeviceMacAddress) && selectedDevice?.TriggersEnabled == true) + var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch) && + ((string.IsNullOrEmpty(p.DeviceMacAddress) && selectedDevice?.TriggersEnabled == true) || Devices.Any(d => d.TriggersEnabled && p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))); if (!presets.Any()) { @@ -753,7 +712,14 @@ namespace ColorControl if (processId > 0) { - context.ForegroundProcess = context.RunningProcesses.FirstOrDefault(p => p.Id == processId); + var process = context.RunningProcesses.FirstOrDefault(p => p.Id == processId); + + if (process?.ProcessName?.Contains(".scr") ?? false) + { + return; + } + + context.ForegroundProcess = process; context.ForegroundProcessIsFullScreen = isFullScreen; } else @@ -851,5 +817,122 @@ namespace ColorControl SelectedDevice = Devices.Any() ? Devices.First() : null; } } + + public void PowerSettingChanged(WindowsPowerSetting setting) + { + var devices = Devices.Where(d => d.PowerByWindows).ToList(); + + if (setting == WindowsPowerSetting.Off) + { + PowerOffDevices(devices, PowerOnOffState.StandBy); + } + else if (setting == WindowsPowerSetting.On) + { + PowerOnDevices(devices); + } + } + + 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; + } + + NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED); + try + { + if (state == PowerOnOffState.ShutDown) + { + var sleep = Config.ShutdownDelay; + + Logger.Debug($"PowerOffDevices: Waiting for {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; + } + } + else if (state == PowerOnOffState.StandBy) + { + var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); + if (File.Exists(standByScript)) + { + Utils.StartProcess(standByScript, hidden: true); + } + } + + Logger.Debug("Powering off tv(s)..."); + var tasks = new List(); + foreach (var device in devices) + { + var task = device.PowerOff(true); + + tasks.Add(task); + } + + var waitTask = Task.WhenAll(tasks.ToArray()); + Utils.WaitForTask(waitTask); + Logger.Debug("Done powering off tv(s)"); + } + finally + { + NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS); + } + } + + public void PowerOnDevices(IEnumerable devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true) + { + if (!devices.Any()) + { + return; + } + + if (checkUserSession && !UserSessionInfo.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.PowerOnAfterManualPowerOff && device.PoweredOffBy == LgDevice.PowerOffSource.Manually) + { + Logger.Debug($"[{device.Name}]: device was manually powered off by user, not powering on"); + continue; + } + + device.DisposeConnection(); + + var task = device.WakeAndConnectWithRetries(Config.PowerOnRetries); + tasks.Add(task); + } + + if (File.Exists(resumeScript)) + { + var waitTask = Task.WhenAll(tasks.ToArray()); + Utils.WaitForTask(waitTask); + + Utils.StartProcess(resumeScript, hidden: true); + } + } } -} +} \ No newline at end of file diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs index 7c9c856..c378320 100644 --- a/ColorControl/MainForm.Designer.cs +++ b/ColorControl/MainForm.Designer.cs @@ -104,9 +104,11 @@ this.lvAmdPresets = new System.Windows.Forms.ListView(); this.tabLG = new System.Windows.Forms.TabPage(); this.scLgController = new System.Windows.Forms.SplitContainer(); + this.cbxLgPcHdmiPort = new System.Windows.Forms.ComboBox(); + this.lblLgPcHdmiPort = new System.Windows.Forms.Label(); + this.edtLgPresetDescription = new System.Windows.Forms.TextBox(); + this.lblLgPresetDescription = new System.Windows.Forms.Label(); this.btnLgDeviceOptionsHelp = new System.Windows.Forms.Button(); - this.lblGameBarShortcut = new System.Windows.Forms.Label(); - this.edtLgGameBarShortcut = new System.Windows.Forms.TextBox(); this.btnLgGameBar = new System.Windows.Forms.Button(); this.btnLgPresetEditTriggerConditions = new System.Windows.Forms.Button(); this.edtLgPresetTriggerConditions = new System.Windows.Forms.TextBox(); @@ -172,6 +174,8 @@ this.lblFixChromeFontsDescription = new System.Windows.Forms.Label(); this.chkFixChromeFonts = new System.Windows.Forms.CheckBox(); this.grpLGOptions = new System.Windows.Forms.GroupBox(); + this.lblGameBarShortcut = new System.Windows.Forms.Label(); + this.edtLgGameBarShortcut = new System.Windows.Forms.TextBox(); this.lblLgOptionShutdownDelayHelp = new System.Windows.Forms.Label(); this.edtLgOptionShutdownDelay = new System.Windows.Forms.NumericUpDown(); this.lblLgOptionShutdownDelay = new System.Windows.Forms.Label(); @@ -201,8 +205,6 @@ this.lbPlugins = new System.Windows.Forms.ListBox(); this.label7 = new System.Windows.Forms.Label(); this.lblInfo = new System.Windows.Forms.Label(); - this.edtLgPresetDescription = new System.Windows.Forms.TextBox(); - this.lblLgPresetDescription = new System.Windows.Forms.Label(); this.tcMain.SuspendLayout(); this.tabNVIDIA.SuspendLayout(); this.mnuNvPresets.SuspendLayout(); @@ -988,11 +990,11 @@ // // scLgController.Panel1 // + this.scLgController.Panel1.Controls.Add(this.cbxLgPcHdmiPort); + this.scLgController.Panel1.Controls.Add(this.lblLgPcHdmiPort); this.scLgController.Panel1.Controls.Add(this.edtLgPresetDescription); this.scLgController.Panel1.Controls.Add(this.lblLgPresetDescription); this.scLgController.Panel1.Controls.Add(this.btnLgDeviceOptionsHelp); - this.scLgController.Panel1.Controls.Add(this.lblGameBarShortcut); - this.scLgController.Panel1.Controls.Add(this.edtLgGameBarShortcut); this.scLgController.Panel1.Controls.Add(this.btnLgGameBar); this.scLgController.Panel1.Controls.Add(this.btnLgPresetEditTriggerConditions); this.scLgController.Panel1.Controls.Add(this.edtLgPresetTriggerConditions); @@ -1040,6 +1042,56 @@ this.scLgController.SplitterWidth = 5; this.scLgController.TabIndex = 43; // + // cbxLgPcHdmiPort + // + this.cbxLgPcHdmiPort.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbxLgPcHdmiPort.FormattingEnabled = true; + this.cbxLgPcHdmiPort.Items.AddRange(new object[] { + "None", + "HDMI1", + "HDMI2", + "HDMI3", + "HDMI4"}); + this.cbxLgPcHdmiPort.Location = new System.Drawing.Point(667, 42); + this.cbxLgPcHdmiPort.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.cbxLgPcHdmiPort.Name = "cbxLgPcHdmiPort"; + this.cbxLgPcHdmiPort.Size = new System.Drawing.Size(86, 23); + this.cbxLgPcHdmiPort.TabIndex = 62; + this.cbxLgPcHdmiPort.SelectedIndexChanged += new System.EventHandler(this.cbxLgPcHdmiPort_SelectedIndexChanged); + // + // lblLgPcHdmiPort + // + this.lblLgPcHdmiPort.AutoSize = true; + this.lblLgPcHdmiPort.Location = new System.Drawing.Point(577, 45); + this.lblLgPcHdmiPort.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblLgPcHdmiPort.Name = "lblLgPcHdmiPort"; + this.lblLgPcHdmiPort.Size = new System.Drawing.Size(84, 15); + this.lblLgPcHdmiPort.TabIndex = 61; + this.lblLgPcHdmiPort.Text = "PC HDMI port:"; + // + // edtLgPresetDescription + // + this.edtLgPresetDescription.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.edtLgPresetDescription.Enabled = false; + this.edtLgPresetDescription.Location = new System.Drawing.Point(100, 469); + this.edtLgPresetDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.edtLgPresetDescription.Multiline = true; + this.edtLgPresetDescription.Name = "edtLgPresetDescription"; + this.edtLgPresetDescription.Size = new System.Drawing.Size(694, 43); + this.edtLgPresetDescription.TabIndex = 60; + // + // lblLgPresetDescription + // + this.lblLgPresetDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.lblLgPresetDescription.AutoSize = true; + this.lblLgPresetDescription.Location = new System.Drawing.Point(6, 474); + this.lblLgPresetDescription.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblLgPresetDescription.Name = "lblLgPresetDescription"; + this.lblLgPresetDescription.Size = new System.Drawing.Size(70, 15); + this.lblLgPresetDescription.TabIndex = 59; + this.lblLgPresetDescription.Text = "Description:"; + // // btnLgDeviceOptionsHelp // this.btnLgDeviceOptionsHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); @@ -1052,28 +1104,6 @@ this.btnLgDeviceOptionsHelp.UseVisualStyleBackColor = true; this.btnLgDeviceOptionsHelp.Click += new System.EventHandler(this.btnLgDeviceOptionsHelp_Click); // - // lblGameBarShortcut - // - this.lblGameBarShortcut.AutoSize = true; - this.lblGameBarShortcut.Location = new System.Drawing.Point(597, 45); - this.lblGameBarShortcut.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblGameBarShortcut.Name = "lblGameBarShortcut"; - this.lblGameBarShortcut.Size = new System.Drawing.Size(55, 15); - this.lblGameBarShortcut.TabIndex = 57; - this.lblGameBarShortcut.Text = "Shortcut:"; - // - // edtLgGameBarShortcut - // - this.edtLgGameBarShortcut.Location = new System.Drawing.Point(667, 42); - this.edtLgGameBarShortcut.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.edtLgGameBarShortcut.Name = "edtLgGameBarShortcut"; - this.edtLgGameBarShortcut.ReadOnly = true; - this.edtLgGameBarShortcut.Size = new System.Drawing.Size(128, 23); - this.edtLgGameBarShortcut.TabIndex = 56; - this.edtLgGameBarShortcut.TextChanged += new System.EventHandler(this.edtLgGameBarShortcut_TextChanged); - this.edtLgGameBarShortcut.KeyDown += new System.Windows.Forms.KeyEventHandler(this.edtLgGameBarShortcut_KeyDown); - this.edtLgGameBarShortcut.KeyUp += new System.Windows.Forms.KeyEventHandler(this.edtLgGameBarShortcut_KeyUp); - // // btnLgGameBar // this.btnLgGameBar.Location = new System.Drawing.Point(478, 40); @@ -1606,7 +1636,8 @@ "Automatically power off on standby", "Automatically power off/on when screensaver activates/deactivates", "Power on even after manual power off", - "Allow triggers to be fired for this device"}); + "Allow triggers to be fired for this device", + "Use Windows power settings"}); this.clbLgPower.Location = new System.Drawing.Point(6, 70); this.clbLgPower.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.clbLgPower.MultiColumn = true; @@ -1805,6 +1836,8 @@ // // grpLGOptions // + this.grpLGOptions.Controls.Add(this.lblGameBarShortcut); + this.grpLGOptions.Controls.Add(this.edtLgGameBarShortcut); this.grpLGOptions.Controls.Add(this.lblLgOptionShutdownDelayHelp); this.grpLGOptions.Controls.Add(this.edtLgOptionShutdownDelay); this.grpLGOptions.Controls.Add(this.lblLgOptionShutdownDelay); @@ -1822,6 +1855,28 @@ this.grpLGOptions.TabStop = false; this.grpLGOptions.Text = "LG controller"; // + // lblGameBarShortcut + // + this.lblGameBarShortcut.AutoSize = true; + this.lblGameBarShortcut.Location = new System.Drawing.Point(201, 127); + this.lblGameBarShortcut.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblGameBarShortcut.Name = "lblGameBarShortcut"; + this.lblGameBarShortcut.Size = new System.Drawing.Size(108, 15); + this.lblGameBarShortcut.TabIndex = 59; + this.lblGameBarShortcut.Text = "Game Bar shortcut:"; + // + // edtLgGameBarShortcut + // + this.edtLgGameBarShortcut.Location = new System.Drawing.Point(307, 121); + this.edtLgGameBarShortcut.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.edtLgGameBarShortcut.Name = "edtLgGameBarShortcut"; + this.edtLgGameBarShortcut.ReadOnly = true; + this.edtLgGameBarShortcut.Size = new System.Drawing.Size(128, 23); + this.edtLgGameBarShortcut.TabIndex = 58; + this.edtLgGameBarShortcut.TextChanged += new System.EventHandler(this.edtLgGameBarShortcut_TextChanged); + this.edtLgGameBarShortcut.KeyDown += new System.Windows.Forms.KeyEventHandler(this.edtLgGameBarShortcut_KeyDown); + this.edtLgGameBarShortcut.KeyUp += new System.Windows.Forms.KeyEventHandler(this.edtLgGameBarShortcut_KeyUp); + // // lblLgOptionShutdownDelayHelp // this.lblLgOptionShutdownDelayHelp.AutoSize = true; @@ -2192,29 +2247,6 @@ this.lblInfo.TabIndex = 1; this.lblInfo.Text = "Info"; // - // edtLgPresetDescription - // - this.edtLgPresetDescription.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.edtLgPresetDescription.Enabled = false; - this.edtLgPresetDescription.Location = new System.Drawing.Point(100, 469); - this.edtLgPresetDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.edtLgPresetDescription.Multiline = true; - this.edtLgPresetDescription.Name = "edtLgPresetDescription"; - this.edtLgPresetDescription.Size = new System.Drawing.Size(694, 43); - this.edtLgPresetDescription.TabIndex = 60; - // - // lblLgPresetDescription - // - this.lblLgPresetDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.lblLgPresetDescription.AutoSize = true; - this.lblLgPresetDescription.Location = new System.Drawing.Point(6, 474); - this.lblLgPresetDescription.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - this.lblLgPresetDescription.Name = "lblLgPresetDescription"; - this.lblLgPresetDescription.Size = new System.Drawing.Size(70, 15); - this.lblLgPresetDescription.TabIndex = 59; - this.lblLgPresetDescription.Text = "Description:"; - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -2440,8 +2472,6 @@ private System.Windows.Forms.CheckBox chkCheckForUpdates; private System.Windows.Forms.CheckBox chkLgShowAdvancedActions; private System.Windows.Forms.Button btnLgGameBar; - private System.Windows.Forms.Label lblGameBarShortcut; - private System.Windows.Forms.TextBox edtLgGameBarShortcut; private System.Windows.Forms.CheckBox chkGdiScaling; private System.Windows.Forms.Button btnLgDeviceOptionsHelp; private System.Windows.Forms.Label lblLgOptionShutdownDelayHelp; @@ -2449,6 +2479,10 @@ private System.Windows.Forms.Label lblLgOptionShutdownDelay; private System.Windows.Forms.TextBox edtLgPresetDescription; private System.Windows.Forms.Label lblLgPresetDescription; + private System.Windows.Forms.Label lblGameBarShortcut; + private System.Windows.Forms.TextBox edtLgGameBarShortcut; + private System.Windows.Forms.ComboBox cbxLgPcHdmiPort; + private System.Windows.Forms.Label lblLgPcHdmiPort; } } diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index 70db8eb..268c3b9 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -15,8 +15,8 @@ using System.Drawing; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; -using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -42,7 +42,6 @@ namespace ColorControl private bool _disableEvents = false; private Config _config; private bool _setVisibleCalled = false; - private RestartDetector _restartDetector; private LgService _lgService; @@ -63,6 +62,7 @@ namespace ColorControl private LgGameBar _gameBarForm; private KeyboardHookManager _keyboardHookManager; + private IntPtr _screenStateNotify; public MainForm(AppContext appContext) { @@ -121,8 +121,7 @@ namespace ColorControl InitInfo(); UserSessionInfo.Install(); - - _restartDetector = new RestartDetector(); + _screenStateNotify = Utils.RegisterPowerSettingNotification(Handle, ref Utils.GUID_CONSOLE_DISPLAY_STATE, 0); //Scale(new SizeF(1.25F, 1.25F)); @@ -183,7 +182,7 @@ namespace ColorControl try { _lgService = new LgService(_dataDir, StartUpParams.RunningFromScheduledTask); - _lgService.RefreshDevices(afterStartUp: true).ContinueWith((_) => BeginInvoke(new Action(AfterLgServiceRefreshDevices))); + _lgService.RefreshDevices(afterStartUp: true).ContinueWith((_) => BeginInvoke(() => AfterLgServiceRefreshDevices())); FillLgPresets(); @@ -232,7 +231,7 @@ namespace ColorControl private void Invoke() { - BeginInvoke(new Action(ToggleGameBar)); + BeginInvoke(() => ToggleGameBar()); } private void AfterLgServiceRefreshDevices() @@ -479,46 +478,13 @@ namespace ColorControl GlobalSave(); - if (SystemShutdown && _lgService.Devices.Any(d => d.PowerOffOnShutdown)) + if (SystemShutdown && _lgService != null) { Logger.Debug($"MainForm_FormClosing: SystemShutdown"); - 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 devices = _lgService.Devices.Where(d => d.PowerOffOnShutdown); - var sleep = _lgService.Config.ShutdownDelay; - - Logger.Debug($"MainForm_FormClosing: Waiting for {sleep} milliseconds..."); - - NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED); - try - { - 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..."); - var task = _lgService.PowerOffOnShutdownOrResume(); - Utils.WaitForTask(task); - Logger.Debug("Done powering off tv"); - //ExecPowerOffPreset(true); - } - finally - { - NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS); - } + _lgService.PowerOffDevices(devices); } } @@ -626,6 +592,26 @@ namespace ColorControl Logger.Debug("WM_BRINGTOFRONT message received, opening form"); OpenForm(this, EventArgs.Empty); } + else if (m.Msg == NativeConstants.WM_SYSCOMMAND) + { + //Logger.Debug($"WM_SYSCOMMAND: {m.WParam.ToInt32()}"); + + if (m.WParam.ToInt32() == NativeConstants.SC_MONITORPOWER) + { + + } + } + else if (m.Msg == NativeConstants.WM_POWERBROADCAST) + { + if (m.WParam.ToInt32() == Utils.PBT_POWERSETTINGCHANGE) + { + var ps = Marshal.PtrToStructure(m.LParam); + + Logger.Debug($"PBT_POWERSETTINGCHANGE: {ps.Data}"); + + _lgService?.PowerSettingChanged((LgService.WindowsPowerSetting)ps.Data); + } + } base.WndProc(ref m); } @@ -668,6 +654,12 @@ namespace ColorControl { // Hide tray icon, otherwise it will remain shown until user mouses over it _trayIcon.Visible = false; + + if (_screenStateNotify != IntPtr.Zero) + { + Utils.UnregisterPowerSettingNotification(_screenStateNotify); + _screenStateNotify = IntPtr.Zero; + } } private void MainForm_Resize(object sender, EventArgs e) @@ -781,7 +773,7 @@ namespace ColorControl if (_lgService == null) { ShowControls(tabLG, false, lblLgError); - lblLgError.Text = "Error while initializing the LG-controller. You either don't have a LG TV or it is disabled."; + lblLgError.Text = "Error while initializing the LG-controller. You either don't have a LG TV, it is disabled or a configuration file is corrupt."; lblLgError.Visible = true; } @@ -1404,6 +1396,11 @@ namespace ColorControl private void InitLgTab() { + if (_lgService == null) + { + return; + } + if (scLgController.Panel2.Controls.Count == 0) { var rcPanel = new RemoteControlPanel(_lgService, _lgService.GetRemoteControlButtons()); @@ -1501,7 +1498,7 @@ namespace ColorControl if (cbxLgApps.Items.Count == 0 && device != null) { - _lgService.RefreshApps().ContinueWith((task) => BeginInvoke(new Action(FillLgApps))); + _lgService.RefreshApps().ContinueWith((task) => BeginInvoke(() => FillLgApps(false))); } btnLgDeviceConvertToCustom.Enabled = devices.Any(); @@ -1526,6 +1523,7 @@ namespace ColorControl clbLgPower.SetItemChecked(4, device?.PowerSwitchOnScreenSaver ?? false); clbLgPower.SetItemChecked(5, device?.PowerOnAfterManualPowerOff ?? false); clbLgPower.SetItemChecked(6, device?.TriggersEnabled ?? true); + clbLgPower.SetItemChecked(7, device?.PowerByWindows ?? false); } finally { @@ -1533,10 +1531,10 @@ namespace ColorControl } } - private void FillLgApps() + private void FillLgApps(bool forced) { var lgApps = _lgService?.GetApps(); - if (lgApps == null || !lgApps.Any()) + if (forced && (lgApps == null || !lgApps.Any())) { MessageForms.WarningOk("Could not refresh the apps. Check the log for details."); return; @@ -1769,7 +1767,7 @@ namespace ColorControl return; } - var applyTask = _lgService.ApplyPreset(preset, reconnect).ContinueWith((task) => BeginInvoke(new Action>(LgPresetApplied), new[] { task })); + var applyTask = _lgService.ApplyPreset(preset, reconnect).ContinueWith((task) => BeginInvoke(() => LgPresetApplied(task))); if (wait) { Utils.WaitForTask(applyTask); @@ -1807,18 +1805,21 @@ namespace ColorControl private void btnLgRefreshApps_Click(object sender, EventArgs e) { - _lgService.RefreshApps(true).ContinueWith((task) => BeginInvoke(new Action(FillLgApps))); + _lgService.RefreshApps(true).ContinueWith((task) => BeginInvoke(() => FillLgApps(true))); } private void cbxLgDevices_SelectedIndexChanged(object sender, EventArgs e) { + cbxLgPcHdmiPort.Enabled = cbxLgDevices.SelectedIndex > -1; if (cbxLgDevices.SelectedIndex == -1) { _lgService.SelectedDevice = null; + cbxLgPcHdmiPort.SelectedIndex = 0; } else { _lgService.SelectedDevice = (LgDevice)cbxLgDevices.SelectedItem; + cbxLgPcHdmiPort.SelectedIndex = _lgService.SelectedDevice.HDMIPortNumber; } chkLgRemoteControlShow.Enabled = _lgService.SelectedDevice != null; btnLgRemoveDevice.Enabled = _lgService.SelectedDevice != null && _lgService.SelectedDevice.IsCustom; @@ -2006,7 +2007,7 @@ You can also activate this option by using the Expert-button and selecting Wake- ); } - BeginInvoke(new Action(() => + BeginInvoke(() => { device.PowerOnAfterStartup = clbLgPower.GetItemChecked(0); device.PowerOnAfterResume = clbLgPower.GetItemChecked(1); @@ -2015,9 +2016,10 @@ You can also activate this option by using the Expert-button and selecting Wake- device.PowerSwitchOnScreenSaver = clbLgPower.GetItemChecked(4); device.PowerOnAfterManualPowerOff = clbLgPower.GetItemChecked(5); device.TriggersEnabled = clbLgPower.GetItemChecked(6); + device.PowerByWindows = clbLgPower.GetItemChecked(7); _lgService.InstallEventHandlers(); - })); + }); } private void edtLgPowerOnAfterResumeDelay_ValueChanged(object sender, EventArgs e) @@ -2123,7 +2125,7 @@ Do you want to continue?"; private void RefreshLgDevices() { - _lgService.RefreshDevices(false).ContinueWith((task) => BeginInvoke(new Action(FillLgDevices))); + _lgService.RefreshDevices(false).ContinueWith((task) => BeginInvoke(() => FillLgDevices())); } private void FillGradient() @@ -2211,7 +2213,7 @@ Do you want to continue?"; private void InitHandleCheckForUpdates(dynamic latest) { - BeginInvoke(new Action(HandleCheckForUpdates), new[] { latest }); + BeginInvoke(() => HandleCheckForUpdates(latest)); } private void HandleCheckForUpdates(dynamic latest) @@ -2267,7 +2269,7 @@ Do you want to continue?"; Task.Run(async () => { await Task.Delay(2000); - BeginInvoke(new Action(() => ApplyNvPresetOnStartup(attempts))); + BeginInvoke(() => ApplyNvPresetOnStartup(attempts)); }); } } @@ -2303,7 +2305,7 @@ Do you want to continue?"; Task.Run(async () => { await Task.Delay(2000); - BeginInvoke(new Action(() => ApplyAmdPresetOnStartup(attempts))); + BeginInvoke(() => ApplyAmdPresetOnStartup(attempts)); }); } } @@ -3131,6 +3133,7 @@ The InStart and Software Update items are now visible under the Expert-button." - 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. "); } @@ -3138,5 +3141,15 @@ The InStart and Software Update items are now visible under the Expert-button." { _lgService.Config.ShutdownDelay = (int)edtLgOptionShutdownDelay.Value; } + + private void cbxLgPcHdmiPort_SelectedIndexChanged(object sender, EventArgs e) + { + if (_lgService?.SelectedDevice == null) + { + return; + } + + _lgService.SelectedDevice.HDMIPortNumber = cbxLgPcHdmiPort.SelectedIndex; + } } } \ No newline at end of file diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs index fcf84b6..dada269 100644 --- a/ColorControl/Program.cs +++ b/ColorControl/Program.cs @@ -118,15 +118,19 @@ namespace ColorControl { ConfigFilename = Path.Combine(DataDir, "Settings.json"); - if (File.Exists(ConfigFilename)) + try { - var data = File.ReadAllText(ConfigFilename); - Config = JsonConvert.DeserializeObject(data); + if (File.Exists(ConfigFilename)) + { + var data = File.ReadAllText(ConfigFilename); + Config = JsonConvert.DeserializeObject(data); + } } - else + catch (Exception ex) { - Config = new Config(); + Logger.Error($"LoadConfig: {ex.Message}"); } + Config ??= new Config(); } private static bool HandleStartupParams(StartUpParams startUpParams, Process existingProcess) diff --git a/ColorControl/Utils.cs b/ColorControl/Utils.cs index 7c92bcd..8b614d6 100644 --- a/ColorControl/Utils.cs +++ b/ColorControl/Utils.cs @@ -53,6 +53,14 @@ namespace ColorControl UserNotificationState.QUNS_PRESENTATION_MODE, }; + [StructLayout(LayoutKind.Sequential, Pack = 4)] + internal struct POWERBROADCAST_SETTING + { + public Guid PowerSetting; + public uint DataLength; + public byte Data; + } + public const int WM_BRINGTOFRONT = NativeConstants.WM_USER + 1; public const int ENUM_CURRENT_SETTINGS = -1; @@ -60,6 +68,8 @@ namespace ColorControl public static string PKEY_PNPX_IpAddress = "{656a3bb3-ecc0-43fd-8477-4ae0404a96cd} 12297"; public static string PKEY_PNPX_PhysicalAddress = "{656a3bb3-ecc0-43fd-8477-4ae0404a96cd} 12294"; + public static Guid GUID_CONSOLE_DISPLAY_STATE = Guid.Parse("6FE69556-704A-47A0-8F24-C28D936FDA47"); + public static int PBT_POWERSETTINGCHANGE = 32787; public delegate void PCREATE_PROCESS_NOTIFY_ROUTINE(IntPtr ParentId, IntPtr ProcessId, bool Create); @@ -96,6 +106,12 @@ namespace ColorControl [DllImport("user32.dll", SetLastError = true)] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + [DllImport(@"User32", SetLastError = true)] + public static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags); + + [DllImport("User32.dll", SetLastError = true)] + public static extern bool UnregisterPowerSettingNotification(IntPtr hWnd); + //[DllImport("Wtsapi32.dll")] //public extern static bool WTSRegisterSessionNotification(IntPtr hWnd, uint dwFlags); diff --git a/ColorControl/lgtv/LgTvApi.cs b/ColorControl/lgtv/LgTvApi.cs index b5e7b93..bdb9d15 100644 --- a/ColorControl/lgtv/LgTvApi.cs +++ b/ColorControl/lgtv/LgTvApi.cs @@ -491,6 +491,13 @@ namespace LgTv await _connection.SubscribeAsync(requestMessage, callback); } + public async Task SubscribeForegroundApp(Func callback, dynamic payload = null) + { + var requestMessage = new RequestMessage("ssap://com.webos.applicationManager/getForegroundAppInfo", null, "subscribe"); + + await _connection.SubscribeAsync(requestMessage, callback); + } + public async Task SubscribePictureSettings(Func callback, dynamic payload = null) { if (payload == null)