mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
* Integrate Mouse Without Borders into PowerToys --------- Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
495 lines
18 KiB
C#
495 lines
18 KiB
C#
// 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.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Principal;
|
|
using System.Windows.Forms;
|
|
|
|
// <summary>
|
|
// Some other helper methods.
|
|
// </summary>
|
|
// <history>
|
|
// 2008 created by Truong Do (ductdo).
|
|
// 2009-... modified by Truong Do (TruongDo).
|
|
// 2023- Included in PowerToys.
|
|
// </history>
|
|
using Microsoft.Win32;
|
|
using MouseWithoutBorders.Class;
|
|
using static System.Windows.Forms.Control;
|
|
|
|
namespace MouseWithoutBorders
|
|
{
|
|
internal partial class Common
|
|
{
|
|
internal const string HELPER_FORM_TEXT = "Mouse without Borders Helper";
|
|
internal const string HelperProcessName = "PowerToys.MouseWithoutBordersHelper";
|
|
private static bool signalHelperToExit;
|
|
private static bool signalWatchDogToExit;
|
|
internal static long WndProcCounter;
|
|
|
|
private static void WatchDogThread()
|
|
{
|
|
long oldCounter = WndProcCounter;
|
|
|
|
do
|
|
{
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
Thread.Sleep(1000);
|
|
|
|
if (signalWatchDogToExit)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
while (BlockingUI)
|
|
{
|
|
Thread.Sleep(1000);
|
|
}
|
|
|
|
if (WndProcCounter == oldCounter)
|
|
{
|
|
Process p = Process.GetCurrentProcess();
|
|
string procInfo = $"{p.PrivateMemorySize64 / 1024 / 1024}MB, {p.TotalProcessorTime}, {Environment.ProcessorCount}.";
|
|
string threadStacks = $"{procInfo} {Thread.DumpThreadsStack()}";
|
|
Common.TelemetryLogTrace(threadStacks, SeverityLevel.Error);
|
|
break;
|
|
}
|
|
|
|
oldCounter = WndProcCounter;
|
|
}
|
|
while (true);
|
|
}
|
|
|
|
private static void HelperThread()
|
|
{
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
_ = EvSwitch.WaitOne(); // Switching to another machine?
|
|
|
|
if (signalHelperToExit)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (Common.NewDesMachineID != Common.MachineID && Common.NewDesMachineID != ID.ALL)
|
|
{
|
|
HideMouseCursor(false);
|
|
Common.MainFormDotEx(true);
|
|
}
|
|
else
|
|
{
|
|
if (Common.SwitchLocation.Count > 0)
|
|
{
|
|
Common.SwitchLocation.Count--;
|
|
|
|
// When we want to move mouse by pixels, we add 300k to x and y (search for XY_BY_PIXEL for other related code).
|
|
Common.LogDebug($"+++++ Moving mouse to {Common.SwitchLocation.X}, {Common.SwitchLocation.Y}");
|
|
|
|
// MaxXY = 65535 so 100k is safe.
|
|
if (Common.SwitchLocation.X > XY_BY_PIXEL - 100000 || Common.SwitchLocation.Y > XY_BY_PIXEL - 100000)
|
|
{
|
|
InputSimulation.MoveMouse(Common.SwitchLocation.X - XY_BY_PIXEL, Common.SwitchLocation.Y - XY_BY_PIXEL);
|
|
}
|
|
else
|
|
{
|
|
InputSimulation.MoveMouseEx(Common.SwitchLocation.X, Common.SwitchLocation.Y);
|
|
}
|
|
|
|
Common.MainFormDot();
|
|
}
|
|
}
|
|
|
|
if (Common.NewDesMachineID == Common.MachineID)
|
|
{
|
|
ReleaseAllKeys();
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log(e);
|
|
}
|
|
|
|
signalHelperToExit = false;
|
|
LogDebug("^^^Helper Thread exiting...^^^");
|
|
}
|
|
|
|
internal static void MainFormDotEx(bool bCheckTS)
|
|
{
|
|
LogDebug("***** MainFormDotEx:");
|
|
|
|
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
|
|
{
|
|
int left = Common.PrimaryScreenBounds.Left + ((Common.PrimaryScreenBounds.Right - Common.PrimaryScreenBounds.Left) / 2) - 1;
|
|
int top = Setting.Values.HideMouse ? 3 : Common.PrimaryScreenBounds.Top + ((Common.PrimaryScreenBounds.Bottom - Common.PrimaryScreenBounds.Top) / 2);
|
|
|
|
Common.MainFormVisible = true;
|
|
|
|
if (Setting.Values.HideMouse && Setting.Values.StealFocusWhenSwitchingMachine && Common.SendMessageToHelper(0x407, new IntPtr(left), new IntPtr(top), true) == 0)
|
|
{
|
|
try
|
|
{
|
|
/* When user just switches to the Logon desktop, user is actually on the "Windows Default Lock Screen" (LockApp).
|
|
* If a click is sent to this during switch, it actually triggers a desktop switch on the local machine causing a reconnection affecting the machine switch.
|
|
* We can detect and skip in this case.
|
|
* */
|
|
IntPtr foreGroundWindow = NativeMethods.GetForegroundWindow();
|
|
string foreGroundWindowText = GetText(foreGroundWindow);
|
|
|
|
bool mouseClick = true;
|
|
|
|
if (foreGroundWindowText.Equals("Windows Default Lock Screen", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
mouseClick = false;
|
|
}
|
|
|
|
// Window title may be localized, check process name:
|
|
if (mouseClick)
|
|
{
|
|
_ = NativeMethods.GetWindowThreadProcessId(foreGroundWindow, out uint pid);
|
|
|
|
if (pid > 0)
|
|
{
|
|
string foreGroundWindowProcess = Process.GetProcessById((int)pid)?.ProcessName;
|
|
|
|
if (foreGroundWindowProcess.Equals("LockApp", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
mouseClick = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mouseClick)
|
|
{
|
|
InputSimulation.MouseClickDotForm(left + 1, top + 1);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
|
}
|
|
|
|
internal static void MainForm3Pixels()
|
|
{
|
|
LogDebug("***** MainFormDotLarge:");
|
|
|
|
DoSomethingInUIThread(
|
|
() =>
|
|
{
|
|
MainForm.Left = Common.PrimaryScreenBounds.Left + ((Common.PrimaryScreenBounds.Right - Common.PrimaryScreenBounds.Left) / 2) - 2;
|
|
MainForm.Top = Setting.Values.HideMouse ? 3 : Common.PrimaryScreenBounds.Top + ((Common.PrimaryScreenBounds.Bottom - Common.PrimaryScreenBounds.Top) / 2) - 1;
|
|
MainForm.Width = 3;
|
|
MainForm.Height = 3;
|
|
MainForm.Opacity = 0.11D;
|
|
MainForm.TopMost = true;
|
|
|
|
if (Setting.Values.HideMouse)
|
|
{
|
|
MainForm.BackColor = Color.Black;
|
|
MainForm.Show();
|
|
Common.MainFormVisible = true;
|
|
}
|
|
else
|
|
{
|
|
MainForm.BackColor = Color.White;
|
|
MainForm.Hide();
|
|
Common.MainFormVisible = false;
|
|
}
|
|
},
|
|
true);
|
|
|
|
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
|
}
|
|
|
|
internal static void MainFormDot()
|
|
{
|
|
DoSomethingInUIThread(
|
|
() =>
|
|
{
|
|
_ = Common.SendMessageToHelper(0x408, IntPtr.Zero, IntPtr.Zero, false);
|
|
|
|
MainForm.Left = Common.PrimaryScreenBounds.Left + ((Common.PrimaryScreenBounds.Right - Common.PrimaryScreenBounds.Left) / 2) - 1;
|
|
MainForm.Top = Setting.Values.HideMouse ? 3 : Common.PrimaryScreenBounds.Top + ((Common.PrimaryScreenBounds.Bottom - Common.PrimaryScreenBounds.Top) / 2);
|
|
MainForm.Width = 1;
|
|
MainForm.Height = 1;
|
|
MainForm.Opacity = 0.15;
|
|
MainForm.Hide();
|
|
Common.MainFormVisible = false;
|
|
},
|
|
true);
|
|
|
|
CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
|
|
}
|
|
|
|
internal static void ToggleIcon()
|
|
{
|
|
try
|
|
{
|
|
if (toggleIconsIndex < TOGGLE_ICONS_SIZE)
|
|
{
|
|
Common.DoSomethingInUIThread(() => Common.MainForm.ChangeIcon(toggleIcons[toggleIconsIndex++]));
|
|
}
|
|
else
|
|
{
|
|
toggleIconsIndex = 0;
|
|
toggleIcons = null;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log(e);
|
|
}
|
|
}
|
|
|
|
internal static void RunDDHelper(bool cleanUp = false)
|
|
{
|
|
if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cleanUp)
|
|
{
|
|
try
|
|
{
|
|
Process[] ps = Process.GetProcessesByName(HelperProcessName);
|
|
foreach (Process p in ps)
|
|
{
|
|
p.KillProcess();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log(e);
|
|
_ = Common.SendMessageToHelper(SharedConst.QUIT_CMD, IntPtr.Zero, IntPtr.Zero);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!Common.IsMyDesktopActive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!Common.IpcChannelCreated)
|
|
{
|
|
TelemetryLogTrace($"{nameof(Common.IpcChannelCreated)} = {Common.IpcChannelCreated}. {GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
|
|
return;
|
|
}
|
|
|
|
if (!MainForm.IsDisposed)
|
|
{
|
|
MainForm.NotifyIcon.Visible = false;
|
|
MainForm.NotifyIcon.Visible = Setting.Values.ShowOriginalUI;
|
|
}
|
|
|
|
IntPtr h = (IntPtr)NativeMethods.FindWindow(null, Common.HELPER_FORM_TEXT);
|
|
|
|
if (h.ToInt32() <= 0)
|
|
{
|
|
_ = Common.CreateProcessInInputDesktopSession(
|
|
$"\"{Path.GetDirectoryName(Application.ExecutablePath)}\\{HelperProcessName}.exe\"",
|
|
string.Empty,
|
|
Common.GetInputDesktop(),
|
|
0);
|
|
|
|
HasSwitchedMachineSinceLastCopy = true;
|
|
|
|
// Common.CreateLowIntegrityProcess("\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"", string.Empty, 0, false, 0);
|
|
if (Process.GetProcessesByName(HelperProcessName)?.Any() != true)
|
|
{
|
|
Log("Unable to start helper process.");
|
|
Common.ShowToolTip("Error starting Mouse Without Borders Helper, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
|
|
}
|
|
else
|
|
{
|
|
Log("Helper process started.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Process.GetProcessesByName(HelperProcessName)?.Any() == true)
|
|
{
|
|
Log("Helper process found running.");
|
|
}
|
|
else
|
|
{
|
|
Log("Invalid helper process found running.");
|
|
Common.ShowToolTip("Error finding Mouse Without Borders Helper, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static int SendMessageToHelper(int msg, IntPtr wparam, IntPtr lparam, bool wait = true, bool log = true)
|
|
{
|
|
int h = NativeMethods.FindWindow(null, Common.HELPER_FORM_TEXT);
|
|
int rv = -1;
|
|
|
|
if (h > 0)
|
|
{
|
|
rv = wait
|
|
? (int)NativeMethods.SendMessage((IntPtr)h, msg, wparam, lparam)
|
|
: NativeMethods.PostMessage((IntPtr)h, msg, wparam, lparam) ? 1 : 0;
|
|
}
|
|
|
|
if (log)
|
|
{
|
|
Common.LogDebug($"SendMessageToHelper: HelperWindow={h}, Return={rv}, msg={msg}, w={wparam.ToInt32()}, l={lparam.ToInt32()}, Post={!wait}");
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
internal static bool IsWindows8AndUp()
|
|
{
|
|
return (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2)
|
|
|| Environment.OSVersion.Version.Major > 6;
|
|
}
|
|
|
|
internal static string GetMiniLog(IEnumerable<ControlCollection> optionControls)
|
|
{
|
|
string log = string.Empty;
|
|
|
|
log += "=============================================================================================================================\r\n";
|
|
log += $"{Application.ProductName} version {Application.ProductVersion}\r\n";
|
|
|
|
log += $"{Setting.Values.Username}/{GetDebugInfo(MyKey)}\r\n";
|
|
log += $"{MachineName}/{MachineID}/{DesMachineID}\r\n";
|
|
log += $"Id: {Setting.Values.DeviceId}\r\n";
|
|
log += $"Matrix: {string.Join(",", MachineMatrix)}\r\n";
|
|
log += $"McPool: {Setting.Values.MachinePoolString}\r\n";
|
|
|
|
log += "\r\nOPTIONS:\r\n";
|
|
|
|
foreach (ControlCollection controlCollection in optionControls)
|
|
{
|
|
foreach (object c in controlCollection)
|
|
{
|
|
if (c is CheckBox checkBox)
|
|
{
|
|
log += $"({(checkBox.Checked ? 1 : 0)}) {checkBox.Text}\r\n";
|
|
continue;
|
|
}
|
|
|
|
if (c is RadioButton radioButton)
|
|
{
|
|
log += $"({(radioButton.Checked ? 1 : 0)}) {radioButton.Name}.[{radioButton.Text}]\r\n";
|
|
continue;
|
|
}
|
|
|
|
if (c is ComboBox comboBox)
|
|
{
|
|
log += $"{comboBox.Name} = {comboBox.Text}\r\n";
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
log += "\r\n";
|
|
|
|
SocketStuff sk = Sk;
|
|
|
|
if (sk?.TcpSockets != null)
|
|
{
|
|
foreach (TcpSk tcp in sk.TcpSockets)
|
|
{
|
|
log += $"{Common.MachineName}{(tcp.IsClient ? "=>" : "<=")}{tcp.MachineName}({tcp.MachineId}):{tcp.Status}\r\n";
|
|
}
|
|
}
|
|
|
|
log += string.Format(CultureInfo.CurrentCulture, "Helper:{0}\r\n", SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero));
|
|
|
|
log += Setting.Values.LastPersonalizeLogonScr + "\r\n";
|
|
log += "Name2IP =\r\n" + Setting.Values.Name2IP + "\r\n";
|
|
|
|
log += "Last 10 trace messages:\r\n";
|
|
|
|
log += string.Join(Environment.NewLine, LogCounter.Select(item => $"({item.Value}): {item.Key}").Take(10));
|
|
|
|
log += "\r\n=============================================================================================================================";
|
|
|
|
return log;
|
|
}
|
|
|
|
internal static bool GetUserName()
|
|
{
|
|
if (string.IsNullOrEmpty(Setting.Values.Username) && !Common.RunOnLogonDesktop)
|
|
{
|
|
if (Program.User.ToLower(CultureInfo.CurrentCulture).Contains("system"))
|
|
{
|
|
_ = Common.ImpersonateLoggedOnUserAndDoSomething(() =>
|
|
{
|
|
Setting.Values.Username = WindowsIdentity.GetCurrent(true).Name;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Setting.Values.Username = Program.User;
|
|
}
|
|
|
|
Common.LogDebug("[Username] = " + Setting.Values.Username);
|
|
}
|
|
|
|
return !string.IsNullOrEmpty(Setting.Values.Username);
|
|
}
|
|
|
|
internal static void ShowOneWayModeMessage()
|
|
{
|
|
ToggleShowTopMostMessage(
|
|
@"
|
|
Due to Security Controls, a remote device cannot control a SAW device.
|
|
Please use the keyboard and Mouse from the SAW device.
|
|
(Press Esc to hide this message)
|
|
",
|
|
string.Empty,
|
|
10);
|
|
}
|
|
|
|
internal static void ApplyCADSetting()
|
|
{
|
|
try
|
|
{
|
|
if (Setting.Values.DisableCAD)
|
|
{
|
|
RegistryKey k = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
|
|
if (k != null)
|
|
{
|
|
k.SetValue("DisableCAD", 1, RegistryValueKind.DWord);
|
|
k.Close();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RegistryKey k = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System");
|
|
if (k != null)
|
|
{
|
|
k.SetValue("DisableCAD", 0, RegistryValueKind.DWord);
|
|
k.Close();
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log(e);
|
|
}
|
|
}
|
|
}
|
|
}
|