// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Application entry and pre-process/initialization. // // // 2008 created by Truong Do (ductdo). // 2009-... modified by Truong Do (TruongDo). // 2023- Included in PowerToys. // using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing.Printing; using System.Globalization; using System.IO; using System.IO.Pipes; using System.Linq; using System.Security.Authentication.ExtendedProtection; using System.Security.Principal; using System.ServiceModel.Channels; using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml.Linq; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library.Utilities; using Microsoft.PowerToys.Telemetry; using MouseWithoutBorders.Core; using Newtonsoft.Json; using StreamJsonRpc; using Logger = MouseWithoutBorders.Core.Logger; using SettingsHelper = Microsoft.PowerToys.Settings.UI.Library.Utilities.Helper; using Thread = MouseWithoutBorders.Core.Thread; [module: SuppressMessage("Microsoft.MSInternal", "CA904:DeclareTypesInMicrosoftOrSystemNamespace", Scope = "namespace", Target = "MouseWithoutBorders", Justification = "Dotnet port with style preservation")] [module: SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant", Justification = "Dotnet port with style preservation")] [module: SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", Scope = "member", Target = "MouseWithoutBorders.Program.#Main()", MessageId = "System.String.ToLower", Justification = "Dotnet port with style preservation")] [module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.Program.#Main()", Justification = "Dotnet port with style preservation")] namespace MouseWithoutBorders.Class { internal static class Program { private static readonly string ServiceName = "PowerToys.MWB.Service"; private static readonly string ServiceModeArg = "UseService"; public static bool ShowServiceModeErrorTooltip; [STAThread] private static void Main() { ManagedCommon.Logger.InitializeLogger("\\MouseWithoutBorders\\Logs"); Logger.Log(Application.ProductName + " Started!"); ETWTrace etwTrace = new ETWTrace(); if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { Logger.Log("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); return; } Thread.CurrentThread.Name = Application.ProductName + " main thread"; Common.BinaryName = Path.GetFileNameWithoutExtension(Application.ExecutablePath); WindowsIdentity currentUser = WindowsIdentity.GetCurrent(); SecurityIdentifier currentUserSID = currentUser.User; SecurityIdentifier localSystemSID = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); bool runningAsSystem = currentUserSID.Equals(localSystemSID); Common.RunWithNoAdminRight = !runningAsSystem; try { string[] args = Environment.GetCommandLineArgs(); string firstArg = string.Empty; if (args.Length > 1 && args[1] != null) { firstArg = args[1].Trim(); } User = WindowsIdentity.GetCurrent().Name; Logger.LogDebug("*** Started as " + User); Logger.Log(Environment.CommandLine); bool serviceMode = firstArg == ServiceModeArg; if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredMwbAllowServiceModeValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { if (runningAsSystem) { Logger.Log("Can't run as a service. It's not allowed according to GPO policy. Please contact your systems administrator."); return; } serviceMode = false; } // If we're started from the .dll module or from the service process, we should // assume the service mode. if (serviceMode && !runningAsSystem) { try { var sc = new ServiceController(ServiceName); sc.Start(); return; } catch (Exception ex) { Logger.Log("Couldn't start the service. Will try to continue as not a service."); Logger.Log(ex); ShowServiceModeErrorTooltip = true; serviceMode = false; Setting.Values.UseService = false; } } if (serviceMode || runningAsSystem) { if (args.Length > 2) { SettingsHelper.UserLocalAppDataPath = args[2].Trim(); } } ShutdownWithPowerToys.WaitForPowerToysRunner(etwTrace); if (firstArg != string.Empty) { if (MachineStuff.CheckSecondInstance(Common.RunWithNoAdminRight)) { Logger.Log("*** Second instance, exiting..."); return; } string myDesktop = WinAPI.GetMyDesktop(); if (firstArg.Equals("winlogon", StringComparison.OrdinalIgnoreCase)) { // Executed by service, running on logon desktop Logger.Log("*** Running on " + firstArg + " desktop"); Common.RunOnLogonDesktop = true; } else if (args[1].Trim().Equals("default", StringComparison.OrdinalIgnoreCase)) { Logger.Log("*** Running on " + firstArg + " desktop"); } else if (args[1].Equals(myDesktop, StringComparison.OrdinalIgnoreCase)) { Logger.Log("*** Running on " + myDesktop); if (myDesktop.Equals("Screen-saver", StringComparison.OrdinalIgnoreCase)) { Common.RunOnScrSaverDesktop = true; Setting.Values.LastX = Common.JUST_GOT_BACK_FROM_SCREEN_SAVER; } } } else { if (MachineStuff.CheckSecondInstance(true)) { Logger.Log("*** Second instance, exiting..."); return; } } PowerToysTelemetry.Log.WriteEvent(new MouseWithoutBorders.Telemetry.MouseWithoutBordersStartedEvent()); try { Common.CurrentProcess = Process.GetCurrentProcess(); Common.CurrentProcess.PriorityClass = ProcessPriorityClass.RealTime; } catch (Exception e) { Logger.Log(e); } Logger.Log(Environment.OSVersion.ToString()); // Environment.OSVersion is unreliable from 6.2 and up, so just forcefully call the APIs and log the exception unsupported by Windows: int setProcessDpiAwarenessResult = -1; try { setProcessDpiAwarenessResult = NativeMethods.SetProcessDpiAwareness(2); Logger.Log(string.Format(CultureInfo.InvariantCulture, "SetProcessDpiAwareness: {0}.", setProcessDpiAwarenessResult)); } catch (DllNotFoundException) { Logger.Log("SetProcessDpiAwareness is unsupported in Windows 7 and lower."); } catch (EntryPointNotFoundException) { Logger.Log("SetProcessDpiAwareness is unsupported in Windows 7 and lower."); } catch (Exception e) { Logger.Log(e); } try { if (setProcessDpiAwarenessResult != 0) { Logger.Log(string.Format(CultureInfo.InvariantCulture, "SetProcessDPIAware: {0}.", NativeMethods.SetProcessDPIAware())); } } catch (Exception e) { Logger.Log(e); } System.Threading.Thread mainUIThread = Thread.CurrentThread; Common.UIThreadID = mainUIThread.ManagedThreadId; Thread.UpdateThreads(mainUIThread); StartInputCallbackThread(); if (!Common.RunOnLogonDesktop) { StartSettingSyncThread(); } Application.EnableVisualStyles(); _ = Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Application.SetCompatibleTextRenderingDefault(false); InitAndCleanup.Init(); Core.Helper.WndProcCounter++; var formScreen = new FrmScreen(); Application.Run(formScreen); etwTrace?.Dispose(); } catch (Exception e) { Logger.Log(e); } } private interface ISettingsSyncHelper { [JsonObject(MemberSerialization.OptIn)] public struct MachineSocketState { // Disable false-positive warning due to IPC #pragma warning disable CS0649 [JsonProperty] public string Name; [JsonProperty] public SocketStatus Status; #pragma warning restore CS0649 } void Shutdown(); void Reconnect(); void GenerateNewKey(); void ConnectToMachine(string machineName, string securityKey); Task RequestMachineSocketStateAsync(); } private sealed class SettingsSyncHelper : ISettingsSyncHelper { public Task RequestMachineSocketStateAsync() { var machineStates = new Dictionary(); if (Common.Sk == null || Common.Sk.TcpSockets == null) { return Task.FromResult(Array.Empty()); } foreach (var client in Common.Sk.TcpSockets .Where(t => t != null && t.IsClient && !string.IsNullOrEmpty(t.MachineName))) { var exists = machineStates.TryGetValue(client.MachineName, out var existingStatus); if (!exists || existingStatus == SocketStatus.NA) { machineStates[client.MachineName] = client.Status; } } return Task.FromResult(machineStates.Select((state) => new ISettingsSyncHelper.MachineSocketState { Name = state.Key, Status = state.Value }).ToArray()); } public void ConnectToMachine(string pcName, string securityKey) { Setting.Values.PauseInstantSaving = true; MachineStuff.ClearComputerMatrix(); Setting.Values.MyKey = securityKey; Encryption.MyKey = securityKey; Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey); MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { pcName.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty }; string[] machines = MachineStuff.MachineMatrix; MachineStuff.MachinePool.Initialize(machines); MachineStuff.UpdateMachinePoolStringSetting(); SocketStuff.InvalidKeyFound = false; InitAndCleanup.ReopenSocketDueToReadError = true; Common.ReopenSockets(true); MachineStuff.SendMachineMatrix(); Setting.Values.PauseInstantSaving = false; Setting.Values.SaveSettings(); } public void GenerateNewKey() { Setting.Values.PauseInstantSaving = true; Setting.Values.EasyMouse = (int)EasyMouseOption.Enable; MachineStuff.ClearComputerMatrix(); Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey(); Encryption.GeneratedKey = true; Setting.Values.PauseInstantSaving = false; Setting.Values.SaveSettings(); Reconnect(); } public void Reconnect() { SocketStuff.InvalidKeyFound = false; InitAndCleanup.ReopenSocketDueToReadError = true; Common.ReopenSockets(true); for (int i = 0; i < 10; i++) { if (Common.AtLeastOneSocketConnected()) { Common.MMSleep(0.5); break; } Common.MMSleep(0.2); } MachineStuff.SendMachineMatrix(); } public void Shutdown() { Process[] ps = Process.GetProcessesByName("PowerToys.MouseWithoutBorders"); Process me = Process.GetCurrentProcess(); foreach (Process p in ps) { if (p.Id != me.Id) { p.Kill(); } } Common.MainForm.Quit(true, false); } } internal static void StartSettingSyncThread() { var serverTaskCancellationSource = new CancellationTokenSource(); CancellationToken cancellationToken = serverTaskCancellationSource.Token; IpcChannel.StartIpcServer("MouseWithoutBorders/SettingsSync", cancellationToken); } internal static void StartInputCallbackThread() { Thread inputCallback = new(new ThreadStart(InputCallbackThread), "InputCallback Thread"); inputCallback.SetApartmentState(ApartmentState.STA); inputCallback.Priority = ThreadPriority.Highest; inputCallback.Start(); } private static void InputCallbackThread() { // SuppressFlow fixes an issue on service mode, where the helper process can't get enough permissions to be started again. // More details can be found on: https://github.com/microsoft/PowerToys/pull/36892 using var asyncFlowControl = ExecutionContext.SuppressFlow(); Common.InputCallbackThreadID = Thread.CurrentThread.ManagedThreadId; while (!InitAndCleanup.InitDone) { Thread.Sleep(100); } Application.Run(new FrmInputCallback()); } internal static void StartService() { if (Common.RunWithNoAdminRight) { return; } try { // Kill all but me Process me = Process.GetCurrentProcess(); Process[] ps = Process.GetProcessesByName(Common.BinaryName); foreach (Process pp in ps) { if (pp.Id != me.Id) { Logger.Log(string.Format(CultureInfo.InvariantCulture, "Killing process {0}.", pp.Id)); pp.KillProcess(); } } } catch (Exception e) { Logger.Log(e); } Service.StartMouseWithoutBordersService(); } internal static string User { get; set; } } }