diff --git a/ColorControl.sln b/ColorControl.sln index db1f57d..c50f372 100644 --- a/ColorControl.sln +++ b/ColorControl.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.0.32014.148 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorControl", "ColorControl\ColorControl.csproj", "{A6005D08-70F0-44CB-A384-9219EBA84F21}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nspector", "Nspector\Nspector.csproj", "{76797290-48F5-4A82-86C3-FAD25DAE4500}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nspector", "Nspector\Nspector.csproj", "{76797290-48F5-4A82-86C3-FAD25DAE4500}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index 7e3690c..332c52e 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -1,32 +1,33 @@  - net6.0-windows10.0.20348.0 - WinExe - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - false - https://www.maassoft.com/downloads/ColorControl/ - ColorControl - Maassoft + 10.0 + net6.0-windows10.0.20348.0 + WinExe + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + false + https://www.maassoft.com/downloads/ColorControl/ + ColorControl + Maassoft Maassoft 0 - 7.3.0.0 - 7.3.0.0 + 8.0.0.0 + 8.0.0.0 false - true - false - true - true - false - true + true + false + true + true + false + true pngbarn.ico 10.0.20348.0 2020-2022 Maassoft @@ -40,68 +41,70 @@ ColorControl_TemporaryKey.pfx - bin\x64\Debug\ - MinimumRecommendedRules.ruleset + bin\x64\Debug\ + MinimumRecommendedRules.ruleset - bin\x64\Release\ - MinimumRecommendedRules.ruleset + bin\x64\Release\ + MinimumRecommendedRules.ruleset - - True - True - Settings.settings - - - Form - - - UserControl - - - - - - PreserveNewest - + + True + True + Settings.settings + + + Form + + + UserControl + + + + + + PreserveNewest + - + - + - - {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} - 1 - 0 - 0 - tlbimp - False - True - + + {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} + 1 + 0 + 0 + tlbimp + False + True + - - + + + - - - - - - + + + + + + + - + - - SettingsSingleFileGenerator - Settings.Designer.cs - + + SettingsSingleFileGenerator + Settings.Designer.cs + \ No newline at end of file diff --git a/ColorControl/Common/PipeUtils.cs b/ColorControl/Common/PipeUtils.cs new file mode 100644 index 0000000..7b300fb --- /dev/null +++ b/ColorControl/Common/PipeUtils.cs @@ -0,0 +1,83 @@ +using ColorControl.Svc; +using Newtonsoft.Json; +using System; +using System.IO.Pipes; +using System.Security.Principal; + +namespace ColorControl.Common +{ + public static class PipeUtils + { + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + public static string SendMessage(string message) + { + var pipeClient = new NamedPipeClientStream(".", "testpipe", PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None); + try + { + pipeClient.Connect(500); + try + { + var ss = new StreamString(pipeClient); + + ss.WriteString(message); + + var result = ss.ReadString(); + + return result; + } + finally + { + pipeClient.Close(); + } + } + catch (Exception ex) + { + Logger.Error(ex, "PipeUtils.SendMessage"); + + return null; + } + } + + public static SvcResultMessage SendMessage(SvcMessage message) + { + var messageJson = JsonConvert.SerializeObject(message); + + var resultJson = SendMessage(messageJson); + + if (resultJson == null) + { + return null; + } + + var resultMessage = JsonConvert.DeserializeObject(resultJson); + + return resultMessage; + } + + public static SvcResultMessage SendMessage(SvcMessageType messageType) + { + var message = new SvcMessage { MessageType = messageType }; + + return SendMessage(message); + } + + public static bool? SendRpcMessage(string command) + { + var message = new SvcMessage { MessageType = SvcMessageType.ExecuteRpc, Data = command }; + + var messageJson = JsonConvert.SerializeObject(message); + + var resultJson = SendMessage(messageJson); + + if (resultJson == null) + { + return null; + } + + var resultMessage = JsonConvert.DeserializeObject(resultJson); + + return resultMessage.Result; + } + } +} diff --git a/ColorControl/Common/StreamString.cs b/ColorControl/Common/StreamString.cs new file mode 100644 index 0000000..4aa829b --- /dev/null +++ b/ColorControl/Common/StreamString.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using System.Text; + +namespace ColorControl.Common +{ + public class StreamString + { + private Stream ioStream; + private UnicodeEncoding streamEncoding; + + public StreamString(Stream ioStream) + { + this.ioStream = ioStream; + streamEncoding = new UnicodeEncoding(); + } + + public string ReadString() + { + var lenBytes = new byte[4]; + + var read = ioStream.Read(lenBytes, 0, lenBytes.Length); + + if (read < lenBytes.Length) + { + return null; + } + + var len = BitConverter.ToInt32(lenBytes); + + var inBuffer = new byte[len]; + ioStream.Read(inBuffer, 0, len); + + return streamEncoding.GetString(inBuffer); + } + + public int WriteString(string outString) + { + var outBuffer = streamEncoding.GetBytes(outString); + var len = outBuffer.Length; + + var lenBytes = BitConverter.GetBytes(len); + + ioStream.Write(lenBytes, 0, lenBytes.Length); + ioStream.Write(outBuffer, 0, len); + ioStream.Flush(); + + return outBuffer.Length + lenBytes.Length; + } + } +} diff --git a/ColorControl/Common/Utils.cs b/ColorControl/Common/Utils.cs index f599500..481a9e7 100644 --- a/ColorControl/Common/Utils.cs +++ b/ColorControl/Common/Utils.cs @@ -14,9 +14,12 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; +using System.Net.NetworkInformation; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Principal; +using System.ServiceProcess; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -49,6 +52,23 @@ namespace ColorControl.Common private static bool WinKeyDown = false; private static Keys[] KeysWithoutModifiers = new[] { Keys.F13, Keys.F14, Keys.F15, Keys.F16, Keys.F17, Keys.F18, Keys.F19, Keys.F20, Keys.F21, Keys.F22, Keys.F23, Keys.F24 }; + private static string SERVICE_NAME = "Color Control Service"; + + public static string ELEVATION_MSG = @"Elevation is needed in some cases where ColorControl needs administrator rights. +Some operations like installing a service, changing the priority of a process or creating a temporary IP-route for improved WOL-functionality will not work without those rights. + +These methods are available: + +- None: no administrator operations can be executed (unless program already manually is started as admin) + +- Run as admin: only available when starting automatically after login (configures scheduled task with highest privileges) + +- Use Windows Service: this will install a Windows Service that handles the operations which need admin rights. This is the preferred method. + +- Use dedicated elevated process: a second ColorControl-process will be spawned that is run as administrator. + +The best and suggested method to provide this is via a Windows Service. Only when installing the service a User Account Control window may popup."; + public static Bitmap SubPixelShift(Bitmap bitmap) { Bitmap bitmap2 = (Bitmap)bitmap.Clone(); @@ -170,15 +190,34 @@ namespace ColorControl.Common return keys; } - static bool IsAdministrator() + private static bool? _IsAdministrator; + + public static bool IsAdministrator() { + if (_IsAdministrator.HasValue) + { + return _IsAdministrator.Value; + } + var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); + _IsAdministrator = principal.IsInRole(WindowsBuiltInRole.Administrator); + + return _IsAdministrator.Value; } - internal static int ExecuteElevated(string args, bool wait = true, bool skipDedicated = false) + internal static int ExecuteElevated(string args, bool wait = true, bool skipDedicated = false, bool skipService = false) { + if (!skipService && IsServiceRunning()) + { + var result = PipeUtils.SendRpcMessage(args); + + if (result.HasValue) + { + return 0; + } + } + if (UseDedicatedElevatedProcess && !skipDedicated) { CheckElevatedProcess(); @@ -188,7 +227,9 @@ namespace ColorControl.Common return 0; } - var info = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName, args) + var fileName = Process.GetCurrentProcess().MainModule.FileName; + + var info = new ProcessStartInfo(fileName, args) { Verb = "runas", // indicates to elevate privileges UseShellExecute = true, @@ -225,11 +266,24 @@ namespace ColorControl.Common if (ElevatedProcessWindowHandle == IntPtr.Zero) { - ExecuteElevated(StartUpParams.StartElevatedParam, false, true); + ExecuteElevated(StartUpParams.StartElevatedParam, false, true, true); - Thread.Sleep(500); + var delay = 1000; - ElevatedProcessWindowHandle = NativeMethods.FindWindowW(null, "ElevatedForm"); + while (delay >= 0) + { + Thread.Sleep(100); + + ElevatedProcessWindowHandle = NativeMethods.FindWindowW(null, "ElevatedForm"); + + if (ElevatedProcessWindowHandle != IntPtr.Zero) + { + Thread.Sleep(100); + break; + } + + delay -= 100; + } } } @@ -494,11 +548,11 @@ namespace ColorControl.Common return list; } - public static void RegisterTask(string taskName, bool enabled) + public static void RegisterTask(string taskName, bool enabled, TaskRunLevel runLevel = TaskRunLevel.LUA) { if (!IsAdministrator()) { - ExecuteElevated(enabled ? StartUpParams.EnableAutoStartParam : StartUpParams.DisableAutoStartParam); + ExecuteElevated(enabled ? $"{StartUpParams.EnableAutoStartParam} {(int)runLevel}" : StartUpParams.DisableAutoStartParam, skipService: true); return; } @@ -511,8 +565,10 @@ namespace ColorControl.Common { if (enabled) { - TaskDefinition td = ts.NewTask(); + var td = ts.NewTask(); + td.RegistrationInfo.Description = "Start ColorControl"; + td.Principal.RunLevel = runLevel; td.Triggers.Add(new LogonTrigger { UserId = WindowsIdentity.GetCurrent().Name }); @@ -528,32 +584,18 @@ namespace ColorControl.Common } catch (Exception e) { - //MessageForms.ErrorOk("Could not create/delete task: " + e.Message); + Logger.Error($"Could not create/delete task: {e.Message}"); } } - public static bool TaskExists(string taskName, bool update) + public static bool TaskExists(string taskName) { var file = Assembly.GetExecutingAssembly().Location; using (TaskService ts = new TaskService()) { var task = ts.RootFolder.Tasks.FirstOrDefault(x => x.Name.Equals(taskName)); - if (task != null) - { - //var action = task.Definition.Actions.FirstOrDefault(x => x.ActionType == TaskActionType.Execute); - //if (action != null) - //{ - // var execAction = action as ExecAction; - // if (!execAction.Path.Equals(file)) - // { - // RegisterTask(taskName, false); - // RegisterTask(taskName, true); - // } - //} - return true; - } - return false; + return task != null; } } @@ -871,5 +913,216 @@ namespace ColorControl.Common { return str1.Trim().Replace(" ", string.Empty).Equals(str2.Trim().Replace(" ", string.Empty), StringComparison.OrdinalIgnoreCase); } + + public static void StartService() + { + if (!IsAdministrator()) + { + ExecuteElevated(StartUpParams.StartServiceParam); + return; + } + + var controller = new ServiceController(SERVICE_NAME); + + if (controller.Status == ServiceControllerStatus.Stopped) + { + controller.Start(); + } + } + + public static void StopService() + { + if (!IsAdministrator()) + { + ExecuteElevated(StartUpParams.StopServiceParam); + return; + } + + var controller = new ServiceController(SERVICE_NAME); + + if (controller.Status == ServiceControllerStatus.Running) + { + controller.Stop(); + } + } + + public static bool IsServiceInstalled() + { + return GetServiceStatus() != 0; + } + + public static bool IsServiceRunning() + { + return GetServiceStatus() == ServiceControllerStatus.Running; + } + + public static ServiceControllerStatus GetServiceStatus() + { + var controller = new ServiceController(SERVICE_NAME); + + try + { + return controller.Status; + } + catch (Exception) + { + return 0; + } + } + + internal static void InstallService() + { + if (!IsAdministrator()) + { + ExecuteElevated(StartUpParams.InstallServiceParam, skipService: true); + + return; + } + + + if (!IsServiceInstalled()) + { + StartProcess("sc.exe", @$"create ""{SERVICE_NAME}"" binpath=""{Process.GetCurrentProcess().MainModule.FileName}"" start=auto", wait: true); + StartProcess("sc.exe", @$"description ""{SERVICE_NAME}"" ""Executes tasks for Color Control that require elevation. If this service is stopped, functions like WOL might be impacted.""", wait: true); + } + + StartService(); + } + + internal static void UninstallService() + { + if (!IsAdministrator()) + { + ExecuteElevated(StartUpParams.UninstallServiceParam, skipService: true); + + return; + } + + if (!IsServiceInstalled()) + { + return; + } + + StopService(); + + StartProcess("sc.exe", @$"delete ""{SERVICE_NAME}""", wait: true); + } + + public static bool PingHost(string nameOrAddress) + { + try + { + using var pinger = new Ping(); + var reply = pinger.Send(nameOrAddress); + return reply.Status == IPStatus.Success; + } + catch (PingException) + { + // Discard PingExceptions and return false; + return false; + } + } + + public static bool WriteText(string fileName, string data) + { + var doWrite = !File.Exists(fileName); + + if (!doWrite) + { + var writtenData = File.ReadAllText(fileName); + + doWrite = !writtenData.Equals(data); + } + + if (doWrite) + { + try + { + File.WriteAllText(fileName, data); + } + catch (Exception e) + { + Logger.Error(e.ToLogString()); + } + } + + return doWrite; + } + + public static string ReadText(string fileName, bool reverse = false) + { + var exists = File.Exists(fileName); + + if (!exists) + { + return null; + } + + try + { + var lines = new StringBuilder(); + + using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + using var reader = new StreamReader(fs); + + string line; + + while ((line = reader.ReadLine()) != null) + { + if (reverse) + { + lines.Insert(0, line + "\r\n"); + continue; + } + + lines.AppendLine(line); + } + + return lines.ToString(); + } + catch (Exception e) + { + Logger.Error(e.ToLogString()); + } + + return null; + } + + public static bool WriteObject(string fileName, object value) + { + var data = JsonConvert.SerializeObject(value); + + return WriteText(fileName, data); + } + + public static T DeserializeJson(string fileName) + { + try + { + var json = File.ReadAllText(fileName); + var data = JsonConvert.DeserializeObject(json); + + return data; + } + catch (Exception e) + { + Logger.Error(e.ToLogString()); + + return default; + } + } + + public static string Base64Encode(string plainText) + { + var plainTextBytes = Encoding.UTF8.GetBytes(plainText); + return Convert.ToBase64String(plainTextBytes); + } + + public static string Base64Decode(string base64EncodedData) + { + var base64EncodedBytes = Convert.FromBase64String(base64EncodedData); + return Encoding.UTF8.GetString(base64EncodedBytes); + } } } diff --git a/ColorControl/Common/WOL.cs b/ColorControl/Common/WOL.cs index 16ccf48..20802b7 100644 --- a/ColorControl/Common/WOL.cs +++ b/ColorControl/Common/WOL.cs @@ -1,67 +1,97 @@ using NLog; using System; -using System.Net.NetworkInformation; +using System.Linq; using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; +using Vanara.PInvoke; +using static Vanara.PInvoke.IpHlpApi; namespace ColorControl.Common { class WOL { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + private const string ArgumentExceptionInvalidMacAddressLength = "Invalid MAC address length."; private const string ArgumentExceptionInvalidPasswordLength = "Invalid password length."; private const int DefaultWolPort = 9; - public static bool WakeFunction(string macAddress) + public static bool WakeFunction(string macAddress, string ipAddress = null) { - return WakeFunctionToAllNics(macAddress); + return WakeFunctionToAllNics(macAddress, ipAddress); } - public static bool WakeFunctionToAllNics(string macAddress) + public static bool WakeFunctionCheckAdmin(string macAddress, string ipAddress = null) + { + if (Utils.IsAdministrator()) + { + return WakeFunction(macAddress, ipAddress); + } + + var result = PipeUtils.SendRpcMessage($"{StartUpParams.SendWakeOnLanParam} {macAddress} {ipAddress}"); + + // Execute local WOL anyway because pipe failed + if (!result.HasValue) + { + result = WakeFunction(macAddress, ipAddress); + } + + return result.Value; + } + + public static bool WakeFunctionToAllNics(string macAddress, string ipAddressString) { var result = false; Logger.Debug($"Sending WOL packet to MAC-address {macAddress}"); try { - var address = PhysicalAddress.Parse(macAddress); - var addressBytes = address.GetAddressBytes(); + var physicalAddress = PhysicalAddress.Parse(macAddress); + var addressBytes = physicalAddress.GetAddressBytes(); var data = GetWolPacket(addressBytes); + var ipAddress = ipAddressString != null ? IPAddress.Parse(ipAddressString) : null; var interfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (var ni in interfaces) { - if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || ni.Name.Contains("VirtualBox")) + if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || ni.Name.Contains("VirtualBox") || ni.Name.Contains("vEthernet") || ni.Description.Contains("Virtual")) { continue; } Logger.Debug($"Found network: {ni.Name} ({ni.Description}), type: {ni.NetworkInterfaceType}"); try { - if (/*ni.OperationalStatus == OperationalStatus.Up &&*/ ni.SupportsMulticast && ni.GetIPProperties().GetIPv4Properties() != null) + if (ni.SupportsMulticast && ni.GetIPProperties().GetIPv4Properties() == null) { - foreach (var uip in ni.GetIPProperties().UnicastAddresses) - { - if (uip.Address.ToString().StartsWith("169.254")) - { - continue; - } - if (uip.Address.AddressFamily == AddressFamily.InterNetwork) - { - Logger.Debug($"Broadcast WOL in network: {ni.Name} ({ni.Description}), local address: {uip.Address}"); - var local = new IPEndPoint(uip.Address, 0); - var udpc = new UdpClient(local); - udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); - udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1); - var target = new IPEndPoint(IPAddress.Broadcast, DefaultWolPort); - udpc.Send(data, data.Length, target); + continue; + } - result = true; - } + foreach (var uip in ni.GetIPProperties().UnicastAddresses) + { + if (uip.Address.ToString().StartsWith("169.254") || uip.Address.AddressFamily != AddressFamily.InterNetwork) + { + continue; } + + Logger.Debug($"Broadcast WOL in network: {ni.Name} ({ni.Description}), local address: {uip.Address}, destination IP-address: {ipAddressString ?? "broadcast"}"); + + if (ipAddress != null) + { + var localEP = new IPEndPoint(uip.Address, 0); + + SendWolToIpAddress(uip.Address, ipAddress, data, physicalAddress); + } + + BroadcastWol(uip.Address, IPAddress.Broadcast, data); + + var parts = uip.Address.ToString().Split(".").ToList(); + parts[3] = "0"; + var broadcastAddress = IPAddress.Parse(string.Join(".", parts)); + BroadcastWol(uip.Address, broadcastAddress, data); + + result = true; } } catch (Exception ex) @@ -110,5 +140,117 @@ namespace ColorControl.Common } return packet; } - } + + private static void BroadcastWol(IPAddress localAddress, IPAddress broadCastAddress, byte[] data) + { + //var localEP = new IPEndPoint(localAddress, 0); + var udpc = new UdpClient(); + + udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); + //udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1); + + var target = new IPEndPoint(broadCastAddress, DefaultWolPort); + + udpc.Send(data, data.Length, target); + } + + private static void SendWolToIpAddress(IPAddress localAddress, IPAddress remoteAddress, byte[] data, PhysicalAddress physicalAddress) + { + //var localEP = new IPEndPoint(localAddress, 0); + + var netLuid = CreateTransientLocalNetEntry(remoteAddress, physicalAddress); + try + { + var udpc = new UdpClient(); + udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); + + var target = new IPEndPoint(remoteAddress, DefaultWolPort); + + udpc.Send(data, data.Length, target); + } + finally + { + DeleteNetEntry(netLuid, remoteAddress); + } + } + + private static NET_LUID CreateTransientLocalNetEntry(IPAddress ipAddress, PhysicalAddress macAddress) + { + var ipBytes = ipAddress.GetAddressBytes(); + + var address = new Ws2_32.SOCKADDR_INET + { + si_family = Ws2_32.ADDRESS_FAMILY.AF_INET, + Ipv4 = new Ws2_32.SOCKADDR_IN(new Ws2_32.IN_ADDR(ipBytes), DefaultWolPort), + }; + + var net = new NET_LUID(); + var addr = new Ws2_32.SOCKADDR_INET(); + + MIB_IPFORWARD_ROW2 forwardRow; + var result = GetBestRoute2(net, 0, addr, address, 0, out forwardRow, out _); + + if (result != Win32Error.ERROR_SUCCESS) + { + Logger.Debug($"Cannot find best route: {result}"); + + var luidDummy = new NET_LUID(); + + return luidDummy; + } + var luid = forwardRow.InterfaceLuid; + + var macAddressBytes = macAddress.GetAddressBytes(); + var bytes = new byte[32]; + + macAddressBytes.CopyTo(bytes, 0); + + var row = new MIB_IPNET_ROW2 + { + Address = address, + InterfaceLuid = luid, + PhysicalAddress = bytes, + PhysicalAddressLength = 6 + }; + + result = CreateIpNetEntry2(ref row); + + if (result != Win32Error.ERROR_SUCCESS) + { + Logger.Debug($"Cannot create ip net entry: {result}"); + } + + return luid; + } + + private static void DeleteNetEntry(NET_LUID luid, IPAddress ipAddress) + { + if (luid.Value == 0) + { + return; + } + + var ipBytes = ipAddress.GetAddressBytes(); + + var address = new Ws2_32.SOCKADDR_INET + { + si_family = Ws2_32.ADDRESS_FAMILY.AF_INET, + Ipv4 = new Ws2_32.SOCKADDR_IN(new Ws2_32.IN_ADDR(ipBytes), DefaultWolPort) + + }; + + var row = new MIB_IPNET_ROW2 + { + Address = address, + InterfaceLuid = luid, + }; + + var result = DeleteIpNetEntry2(ref row); + + if (result != Win32Error.ERROR_SUCCESS) + { + Logger.Debug($"Cannot delete ip net entry: {result}"); + } + } + } } \ No newline at end of file diff --git a/ColorControl/Config.cs b/ColorControl/Config.cs index 6be9acf..788fe61 100644 --- a/ColorControl/Config.cs +++ b/ColorControl/Config.cs @@ -2,6 +2,14 @@ namespace ColorControl { + public enum ElevationMethod + { + None = 0, + RunAsAdmin = 1, + UseService = 2, + UseElevatedProcess = 3 + } + public class ListViewSortState { public SortOrder SortOrder { get; set; } @@ -31,6 +39,8 @@ namespace ColorControl public string LgQuickAccessShortcut { get; set; } public string GameQuickAccessShortcut { get; set; } public bool UseDedicatedElevatedProcess { get; set; } + public ElevationMethod ElevationMethod { get; set; } + public bool ElevationMethodAsked { get; set; } public Config() { @@ -43,6 +53,8 @@ namespace ColorControl FixChromeFonts = false; UseGdiScaling = true; UseDedicatedElevatedProcess = false; + ElevationMethod = ElevationMethod.None; + ElevationMethodAsked = false; LgPresetsSortState = new ListViewSortState(); NvPresetsSortState = new ListViewSortState(); AmdPresetsSortState = new ListViewSortState(); diff --git a/ColorControl/Forms/ElevatedForm.cs b/ColorControl/Forms/ElevatedForm.cs index 044b3f6..f54cc50 100644 --- a/ColorControl/Forms/ElevatedForm.cs +++ b/ColorControl/Forms/ElevatedForm.cs @@ -49,7 +49,7 @@ namespace ColorControl.Forms var container = (WinApi.COPYDATASTRUCT)m.GetLParam(typeof(WinApi.COPYDATASTRUCT)); var message = Marshal.PtrToStringAnsi(container.lpData); - File.AppendAllLines("d:\\Msg.txt", new[] { "Message received: " + message }); + //File.AppendAllLines("d:\\Msg.txt", new[] { "Message received: " + message }); HandleMessage(message); @@ -69,7 +69,14 @@ namespace ColorControl.Forms var startUpParams = StartUpParams.Parse(args); - Program.HandleStartupParams(startUpParams, null); + try + { + Program.HandleStartupParams(startUpParams, null); + } + catch (Exception) + { + + } } private void CheckMutexAsync() diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs index fa80a6f..3969130 100644 --- a/ColorControl/MainForm.Designer.cs +++ b/ColorControl/MainForm.Designer.cs @@ -231,6 +231,13 @@ this.edtDelayDisplaySettings = new System.Windows.Forms.NumericUpDown(); this.label6 = new System.Windows.Forms.Label(); this.grpGeneralOptions = new System.Windows.Forms.GroupBox(); + this.btnStartStopService = new System.Windows.Forms.Button(); + this.btnElevationInfo = new System.Windows.Forms.Button(); + this.rbElevationService = new System.Windows.Forms.RadioButton(); + this.rbElevationProcess = new System.Windows.Forms.RadioButton(); + this.rbElevationAdmin = new System.Windows.Forms.RadioButton(); + this.label8 = new System.Windows.Forms.Label(); + this.rbElevationNone = new System.Windows.Forms.RadioButton(); this.chkGdiScaling = new System.Windows.Forms.CheckBox(); this.chkCheckForUpdates = new System.Windows.Forms.CheckBox(); this.chkMinimizeToSystemTray = new System.Windows.Forms.CheckBox(); @@ -238,6 +245,8 @@ this.chkStartMinimized = new System.Windows.Forms.CheckBox(); this.chkStartAfterLogin = new System.Windows.Forms.CheckBox(); this.tabLog = new System.Windows.Forms.TabPage(); + this.lblShowLog = new System.Windows.Forms.Label(); + this.cbxLogType = new System.Windows.Forms.ComboBox(); this.btnClearLog = new System.Windows.Forms.Button(); this.edtLog = new System.Windows.Forms.TextBox(); this.tabInfo = new System.Windows.Forms.TabPage(); @@ -251,7 +260,6 @@ this.mnuNvSettings = new System.Windows.Forms.ContextMenuStrip(this.components); this.miNvProfileInspector = new System.Windows.Forms.ToolStripMenuItem(); this.miNvSettings = new System.Windows.Forms.ToolStripMenuItem(); - this.chkOptionsDedicatedElevatedProcess = new System.Windows.Forms.CheckBox(); this.tcMain.SuspendLayout(); this.tabNVIDIA.SuspendLayout(); this.mnuNvPresets.SuspendLayout(); @@ -2237,11 +2245,11 @@ this.grpNvidiaOptions.Controls.Add(this.cbxDitheringBitDepth); this.grpNvidiaOptions.Controls.Add(this.chkDitheringEnabled); this.grpNvidiaOptions.Controls.Add(this.pbGradient); - this.grpNvidiaOptions.Location = new System.Drawing.Point(481, 7); + this.grpNvidiaOptions.Location = new System.Drawing.Point(537, 7); this.grpNvidiaOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.grpNvidiaOptions.Name = "grpNvidiaOptions"; this.grpNvidiaOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.grpNvidiaOptions.Size = new System.Drawing.Size(625, 351); + this.grpNvidiaOptions.Size = new System.Drawing.Size(569, 351); this.grpNvidiaOptions.TabIndex = 6; this.grpNvidiaOptions.TabStop = false; this.grpNvidiaOptions.Text = "NVIDIA options - test dithering"; @@ -2312,7 +2320,7 @@ this.pbGradient.Location = new System.Drawing.Point(7, 106); this.pbGradient.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.pbGradient.Name = "pbGradient"; - this.pbGradient.Size = new System.Drawing.Size(611, 236); + this.pbGradient.Size = new System.Drawing.Size(555, 236); this.pbGradient.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; this.pbGradient.TabIndex = 0; this.pbGradient.TabStop = false; @@ -2324,11 +2332,11 @@ this.grpMiscellaneousOptions.Controls.Add(this.edtBlankScreenSaverShortcut); this.grpMiscellaneousOptions.Controls.Add(this.lblFixChromeFontsDescription); this.grpMiscellaneousOptions.Controls.Add(this.chkFixChromeFonts); - this.grpMiscellaneousOptions.Location = new System.Drawing.Point(7, 332); + this.grpMiscellaneousOptions.Location = new System.Drawing.Point(7, 365); this.grpMiscellaneousOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.grpMiscellaneousOptions.Name = "grpMiscellaneousOptions"; this.grpMiscellaneousOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.grpMiscellaneousOptions.Size = new System.Drawing.Size(467, 150); + this.grpMiscellaneousOptions.Size = new System.Drawing.Size(522, 150); this.grpMiscellaneousOptions.TabIndex = 5; this.grpMiscellaneousOptions.TabStop = false; this.grpMiscellaneousOptions.Text = "Miscellaneous"; @@ -2399,11 +2407,11 @@ this.grpLGOptions.Controls.Add(this.lblLgMaxPowerOnRetriesDescription); this.grpLGOptions.Controls.Add(this.edtLgMaxPowerOnRetries); this.grpLGOptions.Controls.Add(this.lblLgMaxPowerOnRetries); - this.grpLGOptions.Location = new System.Drawing.Point(7, 136); + this.grpLGOptions.Location = new System.Drawing.Point(7, 169); this.grpLGOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.grpLGOptions.Name = "grpLGOptions"; this.grpLGOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.grpLGOptions.Size = new System.Drawing.Size(470, 190); + this.grpLGOptions.Size = new System.Drawing.Size(522, 190); this.grpLGOptions.TabIndex = 4; this.grpLGOptions.TabStop = false; this.grpLGOptions.Text = "LG controller"; @@ -2411,7 +2419,7 @@ // lblGameBarShortcut // this.lblGameBarShortcut.AutoSize = true; - this.lblGameBarShortcut.Location = new System.Drawing.Point(201, 127); + this.lblGameBarShortcut.Location = new System.Drawing.Point(266, 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); @@ -2420,7 +2428,7 @@ // // edtLgGameBarShortcut // - this.edtLgGameBarShortcut.Location = new System.Drawing.Point(307, 121); + this.edtLgGameBarShortcut.Location = new System.Drawing.Point(386, 121); this.edtLgGameBarShortcut.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.edtLgGameBarShortcut.Name = "edtLgGameBarShortcut"; this.edtLgGameBarShortcut.ReadOnly = true; @@ -2447,7 +2455,7 @@ 0, 0, 0}); - this.edtLgOptionShutdownDelay.Location = new System.Drawing.Point(349, 68); + this.edtLgOptionShutdownDelay.Location = new System.Drawing.Point(428, 68); this.edtLgOptionShutdownDelay.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.edtLgOptionShutdownDelay.Maximum = new decimal(new int[] { 5000, @@ -2507,7 +2515,7 @@ // // edtLgMaxPowerOnRetries // - this.edtLgMaxPowerOnRetries.Location = new System.Drawing.Point(349, 20); + this.edtLgMaxPowerOnRetries.Location = new System.Drawing.Point(428, 20); this.edtLgMaxPowerOnRetries.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.edtLgMaxPowerOnRetries.Maximum = new decimal(new int[] { 25, @@ -2535,11 +2543,11 @@ // this.grpHDROptions.Controls.Add(this.edtDelayDisplaySettings); this.grpHDROptions.Controls.Add(this.label6); - this.grpHDROptions.Location = new System.Drawing.Point(481, 365); + this.grpHDROptions.Location = new System.Drawing.Point(537, 365); this.grpHDROptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.grpHDROptions.Name = "grpHDROptions"; this.grpHDROptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.grpHDROptions.Size = new System.Drawing.Size(467, 83); + this.grpHDROptions.Size = new System.Drawing.Size(569, 83); this.grpHDROptions.TabIndex = 3; this.grpHDROptions.TabStop = false; this.grpHDROptions.Text = "HDR"; @@ -2578,7 +2586,13 @@ // // grpGeneralOptions // - this.grpGeneralOptions.Controls.Add(this.chkOptionsDedicatedElevatedProcess); + this.grpGeneralOptions.Controls.Add(this.btnStartStopService); + this.grpGeneralOptions.Controls.Add(this.btnElevationInfo); + this.grpGeneralOptions.Controls.Add(this.rbElevationService); + this.grpGeneralOptions.Controls.Add(this.rbElevationProcess); + this.grpGeneralOptions.Controls.Add(this.rbElevationAdmin); + this.grpGeneralOptions.Controls.Add(this.label8); + this.grpGeneralOptions.Controls.Add(this.rbElevationNone); this.grpGeneralOptions.Controls.Add(this.chkGdiScaling); this.grpGeneralOptions.Controls.Add(this.chkCheckForUpdates); this.grpGeneralOptions.Controls.Add(this.chkMinimizeToSystemTray); @@ -2589,15 +2603,101 @@ this.grpGeneralOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.grpGeneralOptions.Name = "grpGeneralOptions"; this.grpGeneralOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.grpGeneralOptions.Size = new System.Drawing.Size(467, 122); + this.grpGeneralOptions.Size = new System.Drawing.Size(522, 156); this.grpGeneralOptions.TabIndex = 2; this.grpGeneralOptions.TabStop = false; this.grpGeneralOptions.Text = "General"; // + // btnStartStopService + // + this.btnStartStopService.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnStartStopService.Location = new System.Drawing.Point(474, 103); + this.btnStartStopService.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.btnStartStopService.Name = "btnStartStopService"; + this.btnStartStopService.Size = new System.Drawing.Size(40, 27); + this.btnStartStopService.TabIndex = 65; + this.btnStartStopService.Text = "Start"; + this.btnStartStopService.UseVisualStyleBackColor = true; + this.btnStartStopService.Click += new System.EventHandler(this.btnStartStopService_Click); + // + // btnElevationInfo + // + this.btnElevationInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnElevationInfo.Location = new System.Drawing.Point(329, 41); + this.btnElevationInfo.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.btnElevationInfo.Name = "btnElevationInfo"; + this.btnElevationInfo.Size = new System.Drawing.Size(22, 27); + this.btnElevationInfo.TabIndex = 64; + this.btnElevationInfo.Text = "?"; + this.btnElevationInfo.UseVisualStyleBackColor = true; + this.btnElevationInfo.Click += new System.EventHandler(this.btnElevationInfo_Click); + // + // rbElevationService + // + this.rbElevationService.AutoSize = true; + this.rbElevationService.Location = new System.Drawing.Point(216, 107); + this.rbElevationService.Name = "rbElevationService"; + this.rbElevationService.Size = new System.Drawing.Size(136, 19); + this.rbElevationService.TabIndex = 63; + this.rbElevationService.TabStop = true; + this.rbElevationService.Tag = "2"; + this.rbElevationService.Text = "Use Windows Service"; + this.rbElevationService.UseVisualStyleBackColor = true; + this.rbElevationService.CheckedChanged += new System.EventHandler(this.rbElevationNone_CheckedChanged); + // + // rbElevationProcess + // + this.rbElevationProcess.AutoSize = true; + this.rbElevationProcess.Location = new System.Drawing.Point(216, 128); + this.rbElevationProcess.Name = "rbElevationProcess"; + this.rbElevationProcess.Size = new System.Drawing.Size(189, 19); + this.rbElevationProcess.TabIndex = 62; + this.rbElevationProcess.TabStop = true; + this.rbElevationProcess.Tag = "3"; + this.rbElevationProcess.Text = "Use dedicated elevated process"; + this.rbElevationProcess.UseVisualStyleBackColor = true; + this.rbElevationProcess.CheckedChanged += new System.EventHandler(this.rbElevationNone_CheckedChanged); + // + // rbElevationAdmin + // + this.rbElevationAdmin.AutoSize = true; + this.rbElevationAdmin.Location = new System.Drawing.Point(216, 86); + this.rbElevationAdmin.Name = "rbElevationAdmin"; + this.rbElevationAdmin.Size = new System.Drawing.Size(97, 19); + this.rbElevationAdmin.TabIndex = 61; + this.rbElevationAdmin.TabStop = true; + this.rbElevationAdmin.Tag = "1"; + this.rbElevationAdmin.Text = "Run as admin"; + this.rbElevationAdmin.UseVisualStyleBackColor = true; + this.rbElevationAdmin.CheckedChanged += new System.EventHandler(this.rbElevationNone_CheckedChanged); + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(216, 47); + this.label8.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(105, 15); + this.label8.TabIndex = 60; + this.label8.Text = "Elevation-method:"; + // + // rbElevationNone + // + this.rbElevationNone.AutoSize = true; + this.rbElevationNone.Location = new System.Drawing.Point(216, 65); + this.rbElevationNone.Name = "rbElevationNone"; + this.rbElevationNone.Size = new System.Drawing.Size(54, 19); + this.rbElevationNone.TabIndex = 10; + this.rbElevationNone.TabStop = true; + this.rbElevationNone.Tag = "0"; + this.rbElevationNone.Text = "None"; + this.rbElevationNone.UseVisualStyleBackColor = true; + this.rbElevationNone.CheckedChanged += new System.EventHandler(this.rbElevationNone_CheckedChanged); + // // chkGdiScaling // this.chkGdiScaling.AutoSize = true; - this.chkGdiScaling.Location = new System.Drawing.Point(238, 72); + this.chkGdiScaling.Location = new System.Drawing.Point(216, 22); this.chkGdiScaling.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.chkGdiScaling.Name = "chkGdiScaling"; this.chkGdiScaling.Size = new System.Drawing.Size(197, 19); @@ -2609,7 +2709,7 @@ // chkCheckForUpdates // this.chkCheckForUpdates.AutoSize = true; - this.chkCheckForUpdates.Location = new System.Drawing.Point(238, 47); + this.chkCheckForUpdates.Location = new System.Drawing.Point(7, 122); this.chkCheckForUpdates.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.chkCheckForUpdates.Name = "chkCheckForUpdates"; this.chkCheckForUpdates.Size = new System.Drawing.Size(197, 19); @@ -2621,7 +2721,7 @@ // chkMinimizeToSystemTray // this.chkMinimizeToSystemTray.AutoSize = true; - this.chkMinimizeToSystemTray.Location = new System.Drawing.Point(238, 22); + this.chkMinimizeToSystemTray.Location = new System.Drawing.Point(7, 97); this.chkMinimizeToSystemTray.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.chkMinimizeToSystemTray.Name = "chkMinimizeToSystemTray"; this.chkMinimizeToSystemTray.Size = new System.Drawing.Size(152, 19); @@ -2667,6 +2767,8 @@ // // tabLog // + this.tabLog.Controls.Add(this.lblShowLog); + this.tabLog.Controls.Add(this.cbxLogType); this.tabLog.Controls.Add(this.btnClearLog); this.tabLog.Controls.Add(this.edtLog); this.tabLog.Location = new System.Drawing.Point(4, 24); @@ -2678,9 +2780,33 @@ this.tabLog.Text = "Log"; this.tabLog.UseVisualStyleBackColor = true; // + // lblShowLog + // + this.lblShowLog.AutoSize = true; + this.lblShowLog.Location = new System.Drawing.Point(8, 14); + this.lblShowLog.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblShowLog.Name = "lblShowLog"; + this.lblShowLog.Size = new System.Drawing.Size(88, 15); + this.lblShowLog.TabIndex = 33; + this.lblShowLog.Text = "Show log from:"; + // + // cbxLogType + // + this.cbxLogType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cbxLogType.FormattingEnabled = true; + this.cbxLogType.Items.AddRange(new object[] { + "Main application", + "Service"}); + this.cbxLogType.Location = new System.Drawing.Point(102, 9); + this.cbxLogType.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.cbxLogType.Name = "cbxLogType"; + this.cbxLogType.Size = new System.Drawing.Size(189, 23); + this.cbxLogType.TabIndex = 32; + this.cbxLogType.SelectedIndexChanged += new System.EventHandler(this.cbxLogType_SelectedIndexChanged); + // // btnClearLog // - this.btnClearLog.Location = new System.Drawing.Point(7, 7); + this.btnClearLog.Location = new System.Drawing.Point(299, 6); this.btnClearLog.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.btnClearLog.Name = "btnClearLog"; this.btnClearLog.Size = new System.Drawing.Size(88, 27); @@ -2824,18 +2950,6 @@ this.miNvSettings.Text = "Settings"; this.miNvSettings.Click += new System.EventHandler(this.miNvSettings_Click); // - // chkOptionsDedicatedElevatedProcess - // - this.chkOptionsDedicatedElevatedProcess.AutoSize = true; - this.chkOptionsDedicatedElevatedProcess.Location = new System.Drawing.Point(7, 97); - this.chkOptionsDedicatedElevatedProcess.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.chkOptionsDedicatedElevatedProcess.Name = "chkOptionsDedicatedElevatedProcess"; - this.chkOptionsDedicatedElevatedProcess.Size = new System.Drawing.Size(190, 19); - this.chkOptionsDedicatedElevatedProcess.TabIndex = 8; - this.chkOptionsDedicatedElevatedProcess.Text = "Use dedicated elevated process"; - this.chkOptionsDedicatedElevatedProcess.UseVisualStyleBackColor = true; - this.chkOptionsDedicatedElevatedProcess.CheckedChanged += new System.EventHandler(this.chkOptionsDedicatedElevatedProcess_CheckedChanged); - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -3124,7 +3238,15 @@ private System.Windows.Forms.ContextMenuStrip mnuGameOptions; private System.Windows.Forms.ToolStripMenuItem miGameProcessorAffinity; private System.Windows.Forms.ToolStripMenuItem miGameProcessPriority; - private System.Windows.Forms.CheckBox chkOptionsDedicatedElevatedProcess; + private System.Windows.Forms.RadioButton rbElevationService; + private System.Windows.Forms.RadioButton rbElevationProcess; + private System.Windows.Forms.RadioButton rbElevationAdmin; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.RadioButton rbElevationNone; + private System.Windows.Forms.Button btnElevationInfo; + private System.Windows.Forms.Button btnStartStopService; + private System.Windows.Forms.Label lblShowLog; + private System.Windows.Forms.ComboBox cbxLogType; } } diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index 645a6f6..aa8eb5f 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -7,8 +7,8 @@ using ColorControl.Services.Common; using ColorControl.Services.GameLauncher; using ColorControl.Services.LG; using ColorControl.Services.NVIDIA; +using ColorControl.Svc; using LgTv; -using Newtonsoft.Json; using NLog; using nspector; using nspector.Common; @@ -28,7 +28,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; using System.Windows.Forms; @@ -77,7 +76,6 @@ namespace ColorControl private bool _updatingDitherSettings; private LgGameBar _gameBarForm; - private QuickAccessForm _gameQuickAccessForm; private GameService _gameService; @@ -125,7 +123,7 @@ namespace ColorControl _trayIcon.ContextMenuStrip.Opened += trayIconContextMenu_Popup; _trayIcon.BalloonTipClicked += trayIconBalloonTip_Clicked; - chkStartAfterLogin.Checked = Utils.TaskExists(Program.TS_TASKNAME, true); + chkStartAfterLogin.Checked = Utils.TaskExists(Program.TS_TASKNAME); chkFixChromeFonts.Enabled = Utils.IsChromeInstalled(); if (chkFixChromeFonts.Enabled) @@ -140,12 +138,15 @@ namespace ColorControl _shortcuts.Add(SHORTCUTID_SCREENSAVER, StartScreenSaver); + //Utils.StartService(); + InitLgService(); InitNvService(); InitAmdService(); InitGameService(); InitInfo(); + UpdateServiceInfo(); UserSessionInfo.Install(); _screenStateNotify = WinApi.RegisterPowerSettingNotification(Handle, ref Utils.GUID_CONSOLE_DISPLAY_STATE, 0); @@ -154,9 +155,36 @@ namespace ColorControl _initialized = true; + //Task.Run(() => Utils.StartPipeAsync()); + AfterInitialized(); } + private void UpdateServiceInfo() + { + var text = "Use Windows Service"; + btnStartStopService.Enabled = true; + + if (Utils.IsServiceRunning()) + { + text += " (running)"; + btnStartStopService.Text = "Stop"; + } + else if (Utils.IsServiceInstalled()) + { + text += " (installed, not running)"; + btnStartStopService.Text = "Start"; + } + else + { + text += " (not installed)"; + btnStartStopService.Text = "Start"; + btnStartStopService.Enabled = false; + } + + rbElevationService.Text = text; + } + private void InitNvService() { try @@ -361,6 +389,11 @@ namespace ColorControl Text = Application.ProductName + " " + Application.ProductVersion; //} + if (Utils.IsAdministrator()) + { + Text += " (administrator)"; + } + lblInfo.Text = Text + " - " + _currentVersionInfo.LegalCopyright; lbPlugins.Items.Add("lgtv.net by gr4b4z"); @@ -817,7 +850,13 @@ namespace ColorControl return; } var enabled = chkStartAfterLogin.Checked; - Utils.RegisterTask(Program.TS_TASKNAME, enabled); + RegisterScheduledTask(enabled); + + rbElevationAdmin.Enabled = enabled; + if (!enabled && rbElevationAdmin.Checked) + { + rbElevationNone.Checked = true; + } } private void LoadConfig() @@ -829,7 +868,6 @@ namespace ColorControl edtDelayDisplaySettings.Value = _config.DisplaySettingsDelay; edtBlankScreenSaverShortcut.Text = _config.ScreenSaverShortcut; chkGdiScaling.Checked = _config.UseGdiScaling; - chkOptionsDedicatedElevatedProcess.Checked = _config.UseDedicatedElevatedProcess; if (!string.IsNullOrEmpty(_config.ScreenSaverShortcut)) { @@ -857,15 +895,7 @@ namespace ColorControl _config.FormHeight = Height; } - try - { - var data = JsonConvert.SerializeObject(_config); - File.WriteAllText(Program.ConfigFilename, data); - } - catch (Exception e) - { - Logger.Error(e.ToLogString()); - } + Utils.WriteObject(Program.ConfigFilename, _config); } private void ShowControls(Control parent, bool show = true, Control exclude = null) @@ -907,6 +937,44 @@ namespace ColorControl InitSelectedTab(); CheckForUpdates(); + + CheckElevationMethod(); + } + + private void CheckElevationMethod() + { + if (Utils.IsAdministrator()) + { + return; + } + + if (_config.ElevationMethodAsked) + { + if (_config.ElevationMethod == ElevationMethod.UseService && !Utils.IsServiceInstalled() && + (MessageForms.QuestionYesNo("The elevation method is set to Windows Service but it is not installed. Do you want to install it now?") == DialogResult.Yes)) + { + Utils.InstallService(); + } + else if (_config.ElevationMethod == ElevationMethod.UseService && !Utils.IsServiceRunning() && + (MessageForms.QuestionYesNo("The elevation method is set to Windows Service but it is not running. Do you want to start it now?") == DialogResult.Yes)) + { + Utils.StartService(); + } + + return; + } + + var result = MessageForms.QuestionYesNo(Utils.ELEVATION_MSG + @" + +Do you want to install and start the Windows Service now? You can always change this on the Options-tab page. + +NOTE: installing the service may cause a User Account Control popup."); + if (result == DialogResult.Yes) + { + SetElevationMethod(ElevationMethod.UseService); + } + + _config.ElevationMethodAsked = true; } protected override void SetVisibleCore(bool value) @@ -1692,6 +1760,24 @@ namespace ColorControl UpdateDitherSettings(); } + + _initialized = false; + try + { + var _ = _config.ElevationMethod switch + { + ElevationMethod.None => rbElevationNone.Checked = true, + ElevationMethod.RunAsAdmin => rbElevationAdmin.Checked = true, + ElevationMethod.UseService => rbElevationService.Checked = true, + ElevationMethod.UseElevatedProcess => rbElevationProcess.Checked = true + }; + } + finally + { + _initialized = true; + } + + UpdateServiceInfo(); } private void UpdateDitherSettings() @@ -2059,36 +2145,44 @@ namespace ColorControl private void LoadLog() { - var filename = Path.Combine(_dataDir, "LogFile.txt"); + var logType = cbxLogType.SelectedIndex; - var lines = new List { "No log file found" }; - - try + if (logType == -1) { - if (File.Exists(filename)) + cbxLogType.SelectedIndex = 0; + return; + } + + string text; + + if (logType == 0) + { + var filename = Path.Combine(_dataDir, "LogFile.txt"); + + text = Utils.ReadText(filename); + + text ??= "No log file found"; + } + else + { + if (Utils.IsServiceRunning()) { - using var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + var message = new SvcMessage { MessageType = SvcMessageType.GetLog }; - using var reader = new StreamReader(fs); + var result = PipeUtils.SendMessage(message); - String line; - - while ((line = reader.ReadLine()) != null) - { - lines.Add(line); - } - - //lines = File.ReadAllLines(filename); + text = result?.Data ?? "Cannot get log from service"; + } + else + { + text = "The service is not installed or not running"; } } - catch (Exception ex) - { - lines = new List { $"Cannot load log file: {ex.Message}" }; - } - var reversedLines = lines.ToList(); - var builder = new StringBuilder(); - reversedLines.ForEach(line => builder.AppendLine(line)); - edtLog.Text = builder.ToString(); + + edtLog.Text = text; + + edtLog.SelectionStart = text.Length; + edtLog.ScrollToCaret(); } private void LoadInfo() @@ -2401,12 +2495,26 @@ Do you want to continue?"; private void btnClearLog_Click(object sender, EventArgs e) { - var filename = Path.Combine(_dataDir, "LogFile.txt"); - if (File.Exists(filename)) + var logType = cbxLogType.SelectedIndex; + + if (logType == -1) { - File.Delete(filename); - edtLog.Clear(); + return; } + + if (logType == 0) + { + var filename = Program.LogFilename; + if (File.Exists(filename)) + { + File.Delete(filename); + } + } + else + { + PipeUtils.SendMessage(SvcMessageType.ClearLog); + } + edtLog.Clear(); } private void edtLgDeviceFilter_TextChanged(object sender, EventArgs e) @@ -3652,7 +3760,8 @@ The InStart and Software Update items are now visible under the Expert-button." var fileName = Path.GetFileNameWithoutExtension(preset.Path); - preset.name = versionInfo.FileDescription ?? versionInfo.ProductName ?? fileName; + preset.name = !string.IsNullOrEmpty(versionInfo.FileDescription) ? versionInfo.FileDescription : + !string.IsNullOrEmpty(versionInfo.ProductName) ? versionInfo.ProductName : fileName; } AddOrUpdateItemGame(preset); @@ -4066,13 +4175,150 @@ The InStart and Software Update items are now visible under the Expert-button." var enumValue = Enum.Parse(typeof(GamePriorityClass), value); preset.ProcessPriorityClass = (uint)(int)enumValue; - } - private void chkOptionsDedicatedElevatedProcess_CheckedChanged(object sender, EventArgs e) + private void rbElevationNone_CheckedChanged(object sender, EventArgs e) { - _config.UseDedicatedElevatedProcess = chkOptionsDedicatedElevatedProcess.Checked; - Utils.UseDedicatedElevatedProcess = _config.UseDedicatedElevatedProcess; + if (!_initialized) + { + return; + } + + + if (sender is not RadioButton button || !button.Checked) + { + return; + } + + SetElevationMethod((ElevationMethod)Utils.ParseInt((string)button.Tag)); + } + + private void SetElevationMethod(ElevationMethod elevationMethod) + { + _config.ElevationMethod = elevationMethod; + + Utils.UseDedicatedElevatedProcess = _config.ElevationMethod == ElevationMethod.UseElevatedProcess; + + var _ = _config.ElevationMethod switch + { + ElevationMethod.None => SetElevationMethodNone(), + ElevationMethod.RunAsAdmin => SetElevationMethodRunAsAdmin(), + ElevationMethod.UseService => SetElevationMethodUseService(), + ElevationMethod.UseElevatedProcess => SetElevationMethodUseElevatedProcess(), + _ => true + }; + + UpdateServiceInfo(); + } + + private bool SetElevationMethodNone() + { + if (chkStartAfterLogin.Enabled) + { + RegisterScheduledTask(); + } + + Utils.UninstallService(); + + return true; + } + + private bool SetElevationMethodRunAsAdmin() + { + if (chkStartAfterLogin.Enabled) + { + RegisterScheduledTask(false); + RegisterScheduledTask(); + } + + Utils.UninstallService(); + + return true; + } + + private bool SetElevationMethodUseService() + { + Utils.InstallService(); + + if (Utils.IsServiceInstalled()) + { + MessageForms.InfoOk("Service installed successfully."); + return true; + } + + MessageForms.ErrorOk("Service could not be installed. Check the logs."); + + return false; + } + + private bool SetElevationMethodUseElevatedProcess() + { + if (chkStartAfterLogin.Enabled) + { + RegisterScheduledTask(); + } + + Utils.UninstallService(); + + return true; + } + + private void RegisterScheduledTask(bool enabled = true) + { + Utils.RegisterTask(Program.TS_TASKNAME, enabled, _config.ElevationMethod == ElevationMethod.RunAsAdmin ? Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest : Microsoft.Win32.TaskScheduler.TaskRunLevel.LUA); + } + + private void btnElevationInfo_Click(object sender, EventArgs e) + { + MessageForms.InfoOk(Utils.ELEVATION_MSG + @$" + +NOTE: if ColorControl itself already runs as administrator this is indicated in the title: ColorControl {Application.ProductVersion} (administrator). + +Currently ColorControl is {(Utils.IsAdministrator() ? "" : "not ")}running as administrator. +"); + } + + private void btnStartStopService_Click(object sender, EventArgs e) + { + btnStartStopService.Enabled = false; + try + { + var start = btnStartStopService.Text == "Start"; + + if (start) + { + Utils.StartService(); + } + else + { + Utils.StopService(); + } + + var wait = 1000; + + while (wait > 0) + { + Utils.WaitForTask(Task.Delay(100)); + + if (start == Utils.IsServiceRunning()) + { + break; + } + + wait -= 100; + } + } + finally + { + btnStartStopService.Enabled = true; + } + + UpdateServiceInfo(); + } + + private void cbxLogType_SelectedIndexChanged(object sender, EventArgs e) + { + LoadLog(); } } } \ No newline at end of file diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs index 10701d4..744bd2c 100644 --- a/ColorControl/Program.cs +++ b/ColorControl/Program.cs @@ -3,13 +3,18 @@ using ColorControl.Forms; using ColorControl.Services.AMD; using ColorControl.Services.LG; using ColorControl.Services.NVIDIA; +using ColorControl.Svc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using NLog; using NWin32; using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; namespace ColorControl @@ -19,6 +24,7 @@ namespace ColorControl public const string TS_TASKNAME = "ColorControl"; public static string DataDir { get; private set; } public static string ConfigFilename { get; private set; } + public static string LogFilename { get; private set; } public static Config Config { get; private set; } public static AppContext AppContext { get; private set; } @@ -32,6 +38,20 @@ namespace ColorControl [STAThread] static void Main(string[] args) { + DataDir = Utils.GetDataPath(); + + var runAsService = args.Contains("--service") || Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true; + + InitLogger(runAsService); + + Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName); + + if (runAsService) + { + Utils.WaitForTask(RunService(args)); + return; + } + MutexId = $"Global\\{typeof(MainForm).GUID}"; var currentDomain = AppDomain.CurrentDomain; @@ -40,9 +60,6 @@ namespace ColorControl // Handler for exceptions in threads behind forms. Application.ThreadException += GlobalThreadExceptionHandler; - DataDir = Utils.GetDataPath(); - InitLogger(); - LoadConfig(); if (Config.UseGdiScaling) @@ -88,10 +105,19 @@ namespace ColorControl mutex.WaitOne(); try { + if (Debugger.IsAttached) + { + Utils.StartService(); + } + Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm(AppContext)); + if (Debugger.IsAttached) + { + Utils.StopService(); + } } catch (Exception ex) { @@ -108,12 +134,17 @@ namespace ColorControl } } - private static void InitLogger() + private static void InitLogger(bool runAsService) { var config = new NLog.Config.LoggingConfiguration(); // Targets where to log to: File and Console - var logfile = new NLog.Targets.FileTarget("logfile") { FileName = Path.Combine(DataDir, "LogFile.txt") }; + var logFileAppendix = runAsService ? "_svc" : ""; + + LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt"); + + var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename }; + //var logconsole = new NLog.Targets.ConsoleTarget("logconsole"); // Rules for mapping loggers to targets @@ -154,7 +185,7 @@ namespace ColorControl } if (startUpParams.EnableAutoStart || startUpParams.DisableAutoStart) { - Utils.RegisterTask(TS_TASKNAME, startUpParams.EnableAutoStart); + Utils.RegisterTask(TS_TASKNAME, startUpParams.EnableAutoStart, startUpParams.AutoStartRunLevel); return true; } if (startUpParams.SetProcessAffinity) @@ -172,6 +203,30 @@ namespace ColorControl StartElevated(); return true; } + if (startUpParams.SendWol) + { + return WOL.WakeFunction(startUpParams.WolMacAddress, startUpParams.WolIpAddress); + } + if (startUpParams.InstallService) + { + Utils.InstallService(); + return true; + } + if (startUpParams.UninstallService) + { + Utils.UninstallService(); + return true; + } + if (startUpParams.StartService) + { + Utils.StartService(); + return true; + } + if (startUpParams.StopService) + { + Utils.StopService(); + return true; + } var useConsole = startUpParams.NoGui || existingProcess != null; @@ -263,6 +318,24 @@ namespace ColorControl } } + private static async Task RunService(string[] args) + { + Logger.Debug("RUNNING SERVICE"); + + using IHost host = Host.CreateDefaultBuilder(args) + .UseWindowsService(options => + { + options.ServiceName = "ColorControl Service"; + }) + .ConfigureServices(services => + { + services.AddHostedService(); + }) + .Build(); + + await host.RunAsync(); + } + public static int EnumThreadWindows(IntPtr handle, IntPtr param) { NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero); diff --git a/ColorControl/Services/AMD/AmdService.cs b/ColorControl/Services/AMD/AmdService.cs index f647bd3..e6671a0 100644 --- a/ColorControl/Services/AMD/AmdService.cs +++ b/ColorControl/Services/AMD/AmdService.cs @@ -1,10 +1,8 @@ using ATI.ADL; using ColorControl.Common; using ColorControl.Services.Common; -using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Windows.Forms; @@ -52,15 +50,7 @@ namespace ColorControl.Services.AMD private void SavePresets() { - try - { - var json = JsonConvert.SerializeObject(_presets); - File.WriteAllText(_presetsFilename, json); - } - catch (Exception e) - { - Logger.Error(e.ToLogString()); - } + Utils.WriteObject(_presetsFilename, _presets); } public void GlobalSave() diff --git a/ColorControl/Services/GameLauncher/GameService.cs b/ColorControl/Services/GameLauncher/GameService.cs index 1041e41..3551b8a 100644 --- a/ColorControl/Services/GameLauncher/GameService.cs +++ b/ColorControl/Services/GameLauncher/GameService.cs @@ -1,9 +1,7 @@ using ColorControl.Common; using ColorControl.Services.Common; -using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; namespace ColorControl.Services.GameLauncher @@ -51,15 +49,7 @@ namespace ColorControl.Services.GameLauncher private void SavePresets() { - try - { - var json = JsonConvert.SerializeObject(_presets); - File.WriteAllText(_presetsFilename, json); - } - catch (Exception e) - { - Logger.Error(e.ToLogString()); - } + Utils.WriteObject(_presetsFilename, _presets); } public void GlobalSave() diff --git a/ColorControl/Services/LG/LgDevice.cs b/ColorControl/Services/LG/LgDevice.cs index 83fba78..79e0130 100644 --- a/ColorControl/Services/LG/LgDevice.cs +++ b/ColorControl/Services/LG/LgDevice.cs @@ -759,7 +759,7 @@ namespace ColorControl.Services.LG if (MacAddress != null) { - result = WOL.WakeFunction(MacAddress); + result = WOL.WakeFunctionCheckAdmin(MacAddress, IpAddress); _justWokeUp = true; } else diff --git a/ColorControl/Services/LG/LgService.cs b/ColorControl/Services/LG/LgService.cs index b012f31..f977831 100644 --- a/ColorControl/Services/LG/LgService.cs +++ b/ColorControl/Services/LG/LgService.cs @@ -1,6 +1,7 @@ using ColorControl.Common; using ColorControl.Forms; using ColorControl.Services.Common; +using ColorControl.Svc; using LgTv; using Microsoft.Win32; using Newtonsoft.Json; @@ -127,6 +128,8 @@ namespace ColorControl.Services.LG SaveConfig(); SavePresets(); SaveRemoteControlButtons(); + + SendConfigToService(); } protected override List GetDefaultPresets() @@ -257,29 +260,19 @@ namespace ColorControl.Services.LG private void SavePresets() { - try - { - var json = JsonConvert.SerializeObject(_presets); - File.WriteAllText(_presetsFilename, json); - } - catch (Exception e) - { - Logger.Error(e.ToLogString()); - } + Utils.WriteObject(_presetsFilename, _presets); } private void SaveConfig() { Config.PowerOnAfterStartup = Devices?.Any(d => d.PowerOnAfterStartup) ?? false; - var json = JsonConvert.SerializeObject(Config); - File.WriteAllText(_configFilename, json); + Utils.WriteObject(_configFilename, Config); } private void SaveRemoteControlButtons() { - var json = JsonConvert.SerializeObject(_remoteControlButtons); - File.WriteAllText(_rcButtonsFilename, json); + Utils.WriteObject(_rcButtonsFilename, _remoteControlButtons); } public async Task RefreshDevices(bool connect = true, bool afterStartUp = false) @@ -937,5 +930,23 @@ namespace ColorControl.Services.LG Utils.StartProcess(resumeScript, hidden: true); } } + + private void SendConfigToService() + { + if (!Utils.IsServiceRunning()) + { + return; + } + + var json = JsonConvert.SerializeObject(Config); + + var message = new SvcMessage + { + MessageType = SvcMessageType.SetLgConfig, + Data = json + }; + + PipeUtils.SendMessage(message); + } } } \ No newline at end of file diff --git a/ColorControl/Services/NVIDIA/NvService.cs b/ColorControl/Services/NVIDIA/NvService.cs index dab957e..f1e5fc9 100644 --- a/ColorControl/Services/NVIDIA/NvService.cs +++ b/ColorControl/Services/NVIDIA/NvService.cs @@ -1,6 +1,5 @@ using ColorControl.Common; using ColorControl.Services.Common; -using Newtonsoft.Json; using nspector.Common; using nspector.Common.Meta; using NvAPIWrapper.Display; @@ -10,7 +9,6 @@ using NWin32.NativeTypes; using System; using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; @@ -187,15 +185,7 @@ namespace ColorControl.Services.NVIDIA private void SavePresets() { - try - { - var json = JsonConvert.SerializeObject(_presets); - File.WriteAllText(_presetsFilename, json); - } - catch (Exception e) - { - Logger.Error(e.ToLogString()); - } + Utils.WriteObject(_presetsFilename, _presets); } private void SetCurrentDisplay(NvPreset preset) @@ -509,11 +499,11 @@ namespace ColorControl.Services.NVIDIA return false; } - var timing = display.DisplayDevice.CurrentTiming; var desktopRect = display.DisplayDevice.ScanOutInformation.SourceDesktopRectangle; if (refreshRate == 0) { + var timing = display.DisplayDevice.CurrentTiming; refreshRate = (uint)timing.Extra.RefreshRate; } @@ -523,11 +513,6 @@ namespace ColorControl.Services.NVIDIA resolutionHeight = (uint)desktopRect.Height; } - if (timing.Extra.RefreshRate == refreshRate && desktopRect.Width == resolutionWidth && desktopRect.Height == resolutionHeight) - { - return true; - } - var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation); return SetRefreshRateInternal(display.Name, (int)refreshRate, portrait, (int)resolutionWidth, (int)resolutionHeight, updateRegistry); diff --git a/ColorControl/StartUpParams.cs b/ColorControl/StartUpParams.cs index 5bc7f0a..aaacc42 100644 --- a/ColorControl/StartUpParams.cs +++ b/ColorControl/StartUpParams.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using ColorControl.Common; +using Microsoft.Win32.TaskScheduler; +using System.Collections.Generic; namespace ColorControl { @@ -17,6 +19,11 @@ namespace ColorControl public const string SetProcessAffinityParam = "--set-process-affinity"; public const string SetProcessPriorityParam = "--set-process-priority"; public const string StartElevatedParam = "--elevated"; + public const string SendWakeOnLanParam = "--send-wol"; + public const string InstallServiceParam = "--install-service"; + public const string UninstallServiceParam = "--uninstall-service"; + public const string StartServiceParam = "--start-service"; + public const string StopServiceParam = "--stop-service"; public bool RunningFromScheduledTask { get; private set; } public bool ActivateChromeFontFix { get; private set; } @@ -30,6 +37,7 @@ namespace ColorControl public string LgPresetName { get; private set; } public bool NoGui { get; set; } public bool EnableAutoStart { get; private set; } + public TaskRunLevel AutoStartRunLevel { get; private set; } = TaskRunLevel.LUA; public bool DisableAutoStart { get; private set; } public bool SetProcessAffinity { get; private set; } public int ProcessId { get; private set; } @@ -37,6 +45,13 @@ namespace ColorControl public bool SetProcessPriority { get; private set; } public uint PriorityClass { get; private set; } public bool StartElevated { get; private set; } + public bool SendWol { get; private set; } + public string WolMacAddress { get; private set; } + public string WolIpAddress { get; private set; } + public bool InstallService { get; private set; } + public bool UninstallService { get; private set; } + public bool StartService { get; private set; } + public bool StopService { get; private set; } public static StartUpParams Parse(IEnumerable args) { @@ -85,6 +100,22 @@ namespace ColorControl settings.PriorityClass = uint.Parse(arg); break; } + case SendWakeOnLanParam: + { + if (settings.WolMacAddress == null) + { + settings.WolMacAddress = arg; + continue; + } + + settings.WolIpAddress = arg; + break; + } + case EnableAutoStartParam: + { + settings.AutoStartRunLevel = (TaskRunLevel)Utils.ParseInt(arg, 0); + break; + } } parseNameParam = string.Empty; @@ -140,6 +171,7 @@ namespace ColorControl case EnableAutoStartParam: { settings.EnableAutoStart = true; + parseNameParam = EnableAutoStartParam; break; } case DisableAutoStartParam: @@ -164,6 +196,32 @@ namespace ColorControl settings.StartElevated = true; break; } + case SendWakeOnLanParam: + { + settings.SendWol = true; + parseNameParam = SendWakeOnLanParam; + break; + } + case InstallServiceParam: + { + settings.InstallService = true; + break; + } + case UninstallServiceParam: + { + settings.UninstallService = true; + break; + } + case StartServiceParam: + { + settings.StartService = true; + break; + } + case StopServiceParam: + { + settings.StopService = true; + break; + } } } return settings; diff --git a/ColorControl/Svc/ColorControlBackgroundService.cs b/ColorControl/Svc/ColorControlBackgroundService.cs new file mode 100644 index 0000000..d3607d4 --- /dev/null +++ b/ColorControl/Svc/ColorControlBackgroundService.cs @@ -0,0 +1,193 @@ +using ColorControl.Common; +using ColorControl.Services.LG; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using NLog; +using System; +using System.IO; +using System.IO.Pipes; +using System.Linq; +using System.Security.AccessControl; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; + +namespace ColorControl.Svc +{ + public sealed class ColorControlBackgroundService : BackgroundService + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static string LgConfigFile = "LgConfig.json"; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + LgConfigFile = Path.Combine(Program.DataDir, LgConfigFile); + + //var lgService = new LgService(Program.DataDir, true); + //lgService.InstallEventHandlers(true); + + await WakeDevicesAsync(); + + var securityIdentifier = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null); + + var ps = new PipeSecurity(); + ps.AddAccessRule(new PipeAccessRule(securityIdentifier, PipeAccessRights.ReadWrite, AccessControlType.Allow)); + + Logger.Debug("EXECUTING BACKGROUND"); + + while (!stoppingToken.IsCancellationRequested) + { + Logger.Debug("WAITING FOR MESSAGE..."); + + var pipeServer = NamedPipeServerStreamAcl.Create("testpipe", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024, 1024, ps); + + await pipeServer.WaitForConnectionAsync(stoppingToken); + + var ss = new StreamString(pipeServer); + + var message = ss.ReadString(); + + //Logger.Debug("MESSAGE RECEIVED: " + message); + + try + { + var result = HandleMessage(message); + + var resultJson = JsonConvert.SerializeObject(result); + + //Logger.Debug("SENDING RESULT: " + resultJson); + + ss.WriteString(resultJson); + } + catch (Exception ex) + { + Logger.Error($"While handling message: {message}"); + + var result = SvcResultMessage.FromResult(false, ex.Message); + var resultJson = JsonConvert.SerializeObject(result); + ss.WriteString(resultJson); + } + + pipeServer.Close(); + //pipeServer.Disconnect(); + } + Logger.Debug("STOPPING..."); + } + catch (OperationCanceledException) + { + Logger.Debug("Service stopping"); + } + catch (Exception ex) + { + Logger.Error(ex, "{Message}", ex.Message); + + // Terminates this process and returns an exit code to the operating system. + // This is required to avoid the 'BackgroundServiceExceptionBehavior', which + // performs one of two scenarios: + // 1. When set to "Ignore": will do nothing at all, errors cause zombie services. + // 2. When set to "StopHost": will cleanly stop the host, and log errors. + // + // In order for the Windows Service Management system to leverage configured + // recovery options, we need to terminate the process with a non-zero exit code. + //Environment.Exit(1); + } + } + + private SvcResultMessage HandleMessage(string json) + { + var message = JsonConvert.DeserializeObject(json); + + var result = message.MessageType switch + { + SvcMessageType.SetLgConfig => HandleSetLgConfigMessage(message), + SvcMessageType.GetLog => HandleGetLogMessage(message), + SvcMessageType.ClearLog => HandleClearLogMessage(message), + SvcMessageType.ExecuteRpc => HandleExecuteRpcCommand(message) + }; + + return result; + } + + private SvcResultMessage HandleExecuteRpcCommand(SvcMessage message) + { + var args = message.Data.Split(' '); + + var startUpParams = StartUpParams.Parse(args); + + return SvcResultMessage.FromResult(Program.HandleStartupParams(startUpParams, null)); + } + + private SvcResultMessage HandleSetLgConfigMessage(SvcMessage message) + { + var data = message.Data; + + Utils.WriteText(LgConfigFile, data); + + return SvcResultMessage.FromResult(true); + } + + private SvcResultMessage HandleGetLogMessage(SvcMessage _) + { + var logFile = Program.LogFilename; + + var data = Utils.ReadText(logFile); + + return SvcResultMessage.FromResult(data); + } + + private SvcResultMessage HandleClearLogMessage(SvcMessage _) + { + var logFile = Program.LogFilename; + + if (File.Exists(logFile)) + { + File.Delete(logFile); + } + + return SvcResultMessage.FromResult(true); + } + + private async Task WakeDevicesAsync() + { + if (!UserSessionInfo.UserLocalSession) + { + return; + } + + if (!File.Exists(LgConfigFile)) + { + return; + } + + var config = Utils.DeserializeJson(LgConfigFile); + + if (config == null) + { + return; + } + + var wakeDevices = config.Devices.Where(d => d.PowerOnAfterStartup).ToList(); + + foreach (var wakeDevice in wakeDevices) + { + for (var i = 0; i < config.PowerOnRetries; i++) + { + WOL.WakeFunction(wakeDevice.MacAddress, wakeDevice.IpAddress); + + await Task.Delay(1000); + + var result = Utils.PingHost(wakeDevice.IpAddress); + + if (result) + { + Logger.Debug($"Device {wakeDevice.Name} is pingable, stopping WOL"); + + break; + } + } + } + } + } +} diff --git a/ColorControl/Svc/SvcMessage.cs b/ColorControl/Svc/SvcMessage.cs new file mode 100644 index 0000000..6e9a499 --- /dev/null +++ b/ColorControl/Svc/SvcMessage.cs @@ -0,0 +1,17 @@ +namespace ColorControl.Svc +{ + public enum SvcMessageType + { + SetLgConfig = 1, + GetLog = 2, + ClearLog = 3, + ExecuteRpc = 10, + } + + public class SvcMessage + { + public SvcMessageType MessageType { get; set; } + + public string Data { get; set; } + } +} diff --git a/ColorControl/Svc/SvcResultMessage.cs b/ColorControl/Svc/SvcResultMessage.cs new file mode 100644 index 0000000..1be9b0a --- /dev/null +++ b/ColorControl/Svc/SvcResultMessage.cs @@ -0,0 +1,15 @@ +namespace ColorControl.Svc +{ + public class SvcResultMessage + { + public bool Result { get; set; } + + public string Data { get; set; } + + public string ErrorMessage { get; set; } + + + public static SvcResultMessage FromResult(bool result, string errorMessage = null) => new SvcResultMessage { Result = result, ErrorMessage = errorMessage }; + public static SvcResultMessage FromResult(string data) => new SvcResultMessage { Result = true, Data = data }; + } +}