From dfede67993b89d3d7097b23f9b8ba11d58159d30 Mon Sep 17 00:00:00 2001 From: Michael Clayton Date: Mon, 5 Jan 2026 04:02:12 +0000 Subject: [PATCH] Ready for Review - [Mouse Without Borders] - refactoring "Common" classes (Part 7 of 7) (#44283) ## Summary of the Pull Request **Part 7** (last part) of a [slow-running 7-part refactor](https://github.com/microsoft/PowerToys/issues/35155#issuecomment-2583334110) of the giant "Common" class in Mouse Without Borders into individual classes with tighter private scope. In this PR: * Extract the "Common" code from the following files: * ```Common.cs```-> ```Core/Common.cs``` * ```IClipboardHelper.cs/Common``` -> ```Core/IpcChannelHelper.cs``` * Update references to the types in the new locations * Update unit test to verify functionality has only changed in an expected way * Make members private or internal as applicable ## PR Checklist - [x] Addresses #35155 - [x] **Communication:** I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end user facing strings can be localized - no changes in this PR - [x] **Dev docs:** Added/updated - no changes in this PR - [x] **New binaries:** Added on the required places - no changes in this PR - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [x] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx - no changes in this PR ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed ### Run manual tests from [Test Checklist Template](https://github.com/microsoft/PowerToys/blob/5bc7201ae2b75b53d3a4bc35119c867ecf71c5f6/doc/releases/tests-checklist-template.md#mouse-without-borders): * Install PowerToys on two PCs in the same local network: - [x] Verify that PowerToys is properly installed on both PCs. - [x] Configure Windows Firewall Rules - ```netsh advfirewall firewall add rule name="PowerToys.MouseWithoutBorders - mc" dir=in action=allow program="C:\src\mc\PowerToys\x64\Debug\PowerToys.exe" enable=yes remoteip=any profile=any protocol=tcp``` * Setup Connection: - [x] Open MWB's settings on the first PC and click the "New Key" button. Verify that a new security key is generated. - [x] Copy the generated security key and paste it in the corresponding input field in the settings of MWB on the second PC. Also enter the name of the first PC in the required field. - [x] Press "Connect" and verify that the machine layout now includes two PC tiles, each displaying their respective PC names. * Verify Connection Status: - [x] Ensure that the border of the remote PC turns green, indicating a successful connection. - [x] Enter an incorrect security key and verify that the border of the remote PC turns red, indicating a failed connection. * Test Remote Mouse/Keyboard Control: - [x] With the PCs connected, test the mouse/keyboard control from one PC to another. Verify that the mouse/keyboard inputs are correctly registered on the other PC. - [ ] Test remote mouse/keyboard control across all four PCs, if available. Verify that inputs are correctly registered on each connected PC when the mouse is active there. - unable to test - only 2 machines available * Test Remote Control with Elevated Apps: - note - the main PowerToys.exe must be running as a **non**-admin for these tests - [x] Open an elevated app on one of the PCs. Verify that without "Use Service" enabled, PowerToys does not control the elevated app. - [x] Enable "Use Service" in MWB's settings (need to run PowerToys.exe as admin to enable "Use Service", then restart PowerToys.exe as non-admin). Verify that PowerToys can now control the elevated app remotely. Verify that MWB processes are running as LocalSystem, while the MWB helper process is running non-elevated. - ```get-process -Name "PowerToys.MouseWithoutBorders*" -IncludeUserName | format-table Id, ProcessName, UserName``` - [x] Process: ```PowerToys.MouseWithoutBorders.exe``` - running as ```SYSTEM``` - [x] Process: ```PowerToys.MouseWithoutBorders.Helper.exe``` - running as current user - ```get-service -Name "PowerToys.*" | ft Status, Name, UserName; get-ciminstance -Class "Win32_Service" -Filter "Name like 'PowerToys%'" | ft ProcessId, Name``` - [x] Service: ```PowerToys.MWB.Service``` - running as ```Local System``` - [x] Toggle "Use Service" again, verify that each time you do that, the MWB processes are restarted. - [x] Run PowerToys elevated on one of the machines, verify that you can control elevated apps remotely now on that machine. * Test Module Enable Status: - [x] For all combinations of "Use Service"/"Run PowerToys as admin", try enabling/disabling MWB module and verify that it's indeed being toggled using task manager. * Test Disconnection/Reconnection: - [x] Disconnect one of the PCs from network. Verify that the machine layout updates to reflect the disconnection. - [x] Do the same, but now by exiting PowerToys. - [x] Start PowerToys again, verify that the PCs are reconnected. * Test Various Local Network Conditions: - [ ] Test MWB performance under various network conditions (e.g., low bandwidth, high latency). Verify that the tool maintains a stable connection and functions correctly. * Clipboard Sharing: - [x] Copy some text on one PC and verify that the same text can be pasted on another PC. - [x] Use the screenshot key and Win+Shift+S to take a screenshot on one PC and verify that the screenshot can be pasted on another PC. - [x] Copy a file in Windows Explorer and verify that the file can be pasted on another PC. Make sure the file size is below 100MB. - [x] Try to copy multiple files and directories and verify that it's not possible (only the first selected file is being copied). * Drag and Drop: - [ ] Drag a file from Windows Explorer on one PC, cross the screen border onto another PC, and release it there. Verify that the file is copied to the other PC. Make sure the file size is below 100MB. - [ ] While dragging the file, verify that a corresponding icon is displayed under the mouse cursor. - [ ] Without moving the mouse from one PC to the target PC, press CTRL+ALT+F1/2/3/4 hotkey to switch to the target PC directly and verify that file sharing/dropping is not working. * Lock and Unlock with "Use Service" Enabled: - [x] Enable "Use Service" in MWB's settings. - [x] Lock a remote PC using Win+L, move the mouse to it remotely, and try to unlock it. Verify that you can unlock the remote PC. - [x] Disable "Use Service" in MWB's settings, lock the remote PC, move the mouse to it remotely, and try to unlock it. Verify that you can't unlock the remote PC. * Test Settings: - [ ] Change the rest of available settings on MWB page and verify that each setting works as described. ### Group Policy Tests See https://learn.microsoft.com/en-us/windows/powertoys/grouppolicy - [ ] Install *.admx / *.adml and check settings behave as expected - [ ] I'll expand the list of settings here when I get this far :-) - [ ] HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys - [x] ConfigureEnabledUtilityMouseWithoutBorders - [x] ```[missing]``` - "Activation -> Enable Mouse Without Borders" enabled, with GPO warning hidden - ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v ConfigureEnabledUtilityMouseWithoutBorders /f``` - [x] ```0``` - "Activation -> Enable Mouse Without Borders" set to "off" and disabled, with GPO warning visible - ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 0 /f``` - [x] ```1``` - "Activation -> Enable Mouse Without Borders" set to "on" and disabled, with GPO warning visible - ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 1 /f``` - [ ] MwbClipboardSharingEnabled - [ ] MwbFileTransferEnabled - [ ] MwbUseOriginalUserInterface - [ ] MwbDisallowBlockingScreensaver - [ ] MwbSameSubnetOnly - [ ] MwbValidateRemoteIp - [x] MwbDisableUserDefinedIpMappingRules - [x] ```[missing]``` - "Advanced Settings -> IP address mapping" enabled, with GPO warning hidden - ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v MwbDisableUserDefinedIpMappingRules /f``` - [x] ```0``` - "Advanced Settings -> IP address mapping" enabled, with GPO warning hidden - ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 0 /f``` - [x] ```1``` - "Advanced Settings -> IP address mapping" disabled, with GPO warning visible - ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 1 /f``` - [x] MwbPolicyDefinedIpMappingRules - [x] ```[missing]``` - "Advanced Settings -> IP address mapping" enabled, with GPO warning and GPO values hidden - ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v MwbPolicyDefinedIpMappingRules /f``` - [x] ```[empty value]``` - "Advanced Settings -> IP address mapping" enabled, with GPO warning hidden and GPO values hidden - ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "" /f``` - [x] ```[non-empty value]``` - "Advanced Settings -> IP address mapping" enabled, with GPO warning visible and GPO values visible - ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "aaa 10.0.0.1\0bbb 10.0.0.2" /f``` --- .../MouseWithoutBorders/App/Class/Common.cs | 1661 ----------------- .../App/Class/IClipboardHelper.cs | 62 +- .../MouseWithoutBorders/App/Core/Clipboard.cs | 6 +- .../MouseWithoutBorders/App/Core/Common.cs | 1654 ++++++++++++++++ .../MouseWithoutBorders/App/Core/Helper.cs | 4 +- .../App/Core/IpcChannelHelper.cs | 53 + .../MouseWithoutBorders/App/Core/Logger.cs | 8 +- .../App/Form/Settings/SetupPage2b.cs | 2 + .../MouseWithoutBorders/App/Form/frmAbout.cs | 2 + .../App/Form/frmMessage.cs | 2 + .../App/Form/frmMouseCursor.cs | 1 + .../Helper/MouseWithoutBordersHelper.csproj | 3 + .../Core/Logger.PrivateDump.expected.txt | 68 +- .../Core/LoggerTests.cs | 1 - 14 files changed, 1770 insertions(+), 1757 deletions(-) delete mode 100644 src/modules/MouseWithoutBorders/App/Class/Common.cs create mode 100644 src/modules/MouseWithoutBorders/App/Core/Common.cs create mode 100644 src/modules/MouseWithoutBorders/App/Core/IpcChannelHelper.cs diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.cs b/src/modules/MouseWithoutBorders/App/Class/Common.cs deleted file mode 100644 index 9a34500b52..0000000000 --- a/src/modules/MouseWithoutBorders/App/Class/Common.cs +++ /dev/null @@ -1,1661 +0,0 @@ -// 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.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Drawing; -using System.Drawing.Imaging; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Windows.Forms; - -using Microsoft.PowerToys.Settings.UI.Library; - -// -// Most of the helper methods. -// -// -// 2008 created by Truong Do (ductdo). -// 2009-... modified by Truong Do (TruongDo). -// 2023- Included in PowerToys. -// -using MouseWithoutBorders.Class; -using MouseWithoutBorders.Core; -using MouseWithoutBorders.Exceptions; - -using Clipboard = MouseWithoutBorders.Core.Clipboard; -using Thread = MouseWithoutBorders.Core.Thread; - -// Log is enough -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckClipboard()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetAsStartupItem(System.Boolean)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HelperThread()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetMyStorageDir()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#MouseEvent(MouseWithoutBorders.MOUSEDATA)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#KeybdEvent(MouseWithoutBorders.KEYBDDATA)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartMouseWithoutBordersService()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HookClipboard()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardData(MouseWithoutBorders.DATA,System.Boolean)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiverCallback(System.Object)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckNewVersion()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartServiceAndSendLogoffSignal()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetScreenConfig()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CaptureScreen()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#InitEncryption()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ToggleIcon()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNameAndIPAddresses()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#Cleanup()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "MouseWithoutBorders.Common", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ProcessPackage(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#get_Machine_Pool()", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean,System.String)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNewImageAndSaveTo(System.String,System.String)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#LogAll()", MessageId = "System.String.Format(System.IFormatProvider,System.String,System.Object[])", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#DragDropStep04()", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", MessageId = "MouseWithoutBorders.NativeMethods.WaitForSingleObject(System.IntPtr,System.Int32)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#GetText(System.IntPtr)", MessageId = "MouseWithoutBorders.NativeMethods.GetWindowText(System.IntPtr,System.Text.StringBuilder,System.Int32)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", MessageId = "MouseWithoutBorders.NativeMethods.WTSQueryUserToken(System.UInt32,System.IntPtr@)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32,System.Boolean,System.Int64)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateProcessInInputDesktopSession(System.String,System.String,System.String,System.Boolean,System.Int16)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SkSend(MouseWithoutBorders.DATA,System.Boolean,System.Int32)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardDataUsingTCP(MouseWithoutBorders.DATA,System.Boolean,System.Net.Sockets.Socket)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#UpdateMachineMatrix(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")] -[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReopenSockets(System.Boolean)", Justification = "Dotnet port with style preservation")] - -namespace MouseWithoutBorders -{ - internal partial class Common - { - internal Common() - { - } - - private static InputHook hook; - private static FrmMatrix matrixForm; - private static FrmInputCallback inputCallbackForm; - private static FrmAbout aboutForm; -#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter - internal static Thread helper; - internal static int screenWidth; - internal static int screenHeight; -#pragma warning restore SA1307 - private static int lastX; - private static int lastY; - - private static bool mainFormVisible = true; - private static bool runOnLogonDesktop; - private static bool runOnScrSaverDesktop; - -#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter - internal static int[] toggleIcons; - internal static int toggleIconsIndex; -#pragma warning restore SA1307 - internal const int TOGGLE_ICONS_SIZE = 4; - internal const int ICON_ONE = 0; - internal const int ICON_ALL = 1; - internal const int ICON_SMALL_CLIPBOARD = 2; - internal const int ICON_BIG_CLIPBOARD = 3; - internal const int ICON_ERROR = 4; - internal const int JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999; - - internal const int NETWORK_STREAM_BUF_SIZE = 1024 * 1024; - internal static readonly EventWaitHandle EvSwitch = new(false, EventResetMode.AutoReset); - private static Point lastPos; -#pragma warning disable SA1307 // Accessible fields should begin with upper-case names - internal static int switchCount; -#pragma warning restore SA1307 - private static long lastReconnectByHotKeyTime; -#pragma warning disable SA1307 // Accessible fields should begin with upper-case names - internal static int tcpPort; -#pragma warning restore SA1307 - private static bool secondOpenSocketTry; - private static string binaryName; - - internal static Process CurrentProcess { get; set; } - - internal static bool HotkeyMatched(int vkCode, bool winDown, bool ctrlDown, bool altDown, bool shiftDown, HotkeySettings hotkey) - { - return !hotkey.IsEmpty() && (vkCode == hotkey.Code) && (!hotkey.Win || winDown) && (!hotkey.Alt || altDown) && (!hotkey.Shift || shiftDown) && (!hotkey.Ctrl || ctrlDown); - } - - public static string BinaryName - { - get => Common.binaryName; - set => Common.binaryName = value; - } - - public static bool SecondOpenSocketTry - { - get => Common.secondOpenSocketTry; - set => Common.secondOpenSocketTry = value; - } - - public static long LastReconnectByHotKeyTime - { - get => Common.lastReconnectByHotKeyTime; - set => Common.lastReconnectByHotKeyTime = value; - } - - public static int SwitchCount - { - get => Common.switchCount; - set => Common.switchCount = value; - } - - public static Point LastPos - { - get => Common.lastPos; - set => Common.lastPos = value; - } - - internal static FrmAbout AboutForm - { - get => Common.aboutForm; - set => Common.aboutForm = value; - } - - internal static FrmInputCallback InputCallbackForm - { - get => Common.inputCallbackForm; - set => Common.inputCallbackForm = value; - } - - public static int PaintCount { get; set; } - - internal static bool RunOnScrSaverDesktop - { - get => Common.runOnScrSaverDesktop; - set => Common.runOnScrSaverDesktop = value; - } - - internal static bool RunOnLogonDesktop - { - get => Common.runOnLogonDesktop; - set => Common.runOnLogonDesktop = value; - } - - internal static bool RunWithNoAdminRight { get; set; } - - internal static int LastX - { - get => Common.lastX; - set => Common.lastX = value; - } - - internal static int LastY - { - get => Common.lastY; - set => Common.lastY = value; - } - - internal static int[] ToggleIcons => Common.toggleIcons; - - internal static int ScreenHeight => Common.screenHeight; - - internal static int ScreenWidth => Common.screenWidth; - - internal static bool Is64bitOS - { - get; set; - - // set { Common.is64bitOS = value; } - } - - internal static int ToggleIconsIndex - { - // get { return Common.toggleIconsIndex; } - set => Common.toggleIconsIndex = value; - } - - internal static InputHook Hook - { - get => Common.hook; - set => Common.hook = value; - } - - internal static SocketStuff Sk { get; set; } - - internal static FrmScreen MainForm { get; set; } - - internal static FrmMouseCursor MouseCursorForm { get; set; } - - internal static FrmMatrix MatrixForm - { - get => Common.matrixForm; - set => Common.matrixForm = value; - } - - internal static ID DesMachineID - { - get => MachineStuff.desMachineID; - - set - { - MachineStuff.desMachineID = value; - MachineStuff.DesMachineName = MachineStuff.NameFromID(MachineStuff.desMachineID); - } - } - - internal static ID MachineID => (ID)Setting.Values.MachineId; - - internal static string MachineName { get; set; } - - internal static bool MainFormVisible - { - get => Common.mainFormVisible; - set => Common.mainFormVisible = value; - } - - internal static Mutex SocketMutex { get; set; } // Synchronization between MouseWithoutBorders running in different desktops - - // TODO: For telemetry only, to be removed. - private static int socketMutexBalance; - - internal static void ReleaseSocketMutex() - { - if (SocketMutex != null) - { - Logger.LogDebug("SOCKET MUTEX BEGIN RELEASE."); - - try - { - _ = Interlocked.Decrement(ref socketMutexBalance); - SocketMutex.ReleaseMutex(); - } - catch (ApplicationException e) - { - // The current thread does not own the mutex, the thread acquired it will own it. - Logger.TelemetryLogTrace($"{nameof(ReleaseSocketMutex)}: {e.Message}. {Thread.CurrentThread.ManagedThreadId}/{UIThreadID}.", SeverityLevel.Warning); - } - - Logger.LogDebug("SOCKET MUTEX RELEASED."); - } - else - { - Logger.LogDebug("SOCKET MUTEX NULL."); - } - } - - internal static void AcquireSocketMutex() - { - if (SocketMutex != null) - { - Logger.LogDebug("SOCKET MUTEX BEGIN WAIT."); - int waitTimeout = 60000; // TcpListener.Stop may take very long to complete for some reason. - - int socketMutexBalance = int.MinValue; - - bool acquireMutex = ExecuteAndTrace( - "Waiting for sockets to close", - () => - { - socketMutexBalance = Interlocked.Increment(ref Common.socketMutexBalance); - _ = SocketMutex.WaitOne(waitTimeout); // The app now requires .Net 4.0. Note: .Net20RTM does not have the one-parameter version of the API. - }, - TimeSpan.FromSeconds(5)); - - // Took longer than expected. - if (!acquireMutex) - { - Process[] ps = Process.GetProcessesByName(Common.BinaryName); - Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {WinAPI.IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {WinAPI.GetMyDesktop()}/{WinAPI.GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning); - } - - Logger.LogDebug("SOCKET MUTEX ENDED."); - } - else - { - Logger.LogDebug("SOCKET MUTEX NULL."); - } - } - - internal static bool BlockingUI { get; private set; } - - internal static bool ExecuteAndTrace(string actionName, Action action, TimeSpan timeout, bool restart = false) - { - bool rv = true; - Logger.LogDebug(actionName); - bool done = false; - - BlockingUI = true; - - if (restart) - { - Common.MainForm.Text = Setting.Values.MyIdEx; - - /* closesocket() rarely gets stuck for some reason inside ntdll!ZwClose ...=>... afd!AfdCleanupCore. - * There is no good workaround for it so far, still working with [Winsock 2.0 Discussions] to address the issue. - * */ - new Thread( - () => - { - for (int i = 0; i < timeout.TotalSeconds; i++) - { - Thread.Sleep(1000); - - if (done) - { - return; - } - } - - Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}, restarting the process.", SeverityLevel.Warning, true); - - string desktop = WinAPI.GetMyDesktop(); - MachineStuff.oneInstanceCheck?.Close(); - _ = Process.Start(Application.ExecutablePath, desktop); - Logger.LogDebug($"Started on desktop {desktop}"); - - Process.GetCurrentProcess().KillProcess(true); - }, - $"{actionName} watchdog").Start(); - } - - Stopwatch timer = Stopwatch.StartNew(); - - try - { - action(); - } - finally - { - done = true; - BlockingUI = false; - - if (restart) - { - Common.MainForm.Text = Setting.Values.MyID; - } - - timer.Stop(); - - if (timer.Elapsed > timeout) - { - rv = false; - - if (!restart) - { - Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}: {(long)timer.Elapsed.TotalSeconds}.", SeverityLevel.Warning); - } - } - } - - return rv; - } - - internal static byte[] GetBytes(string st) - { - return ASCIIEncoding.ASCII.GetBytes(st); - } - - internal static string GetString(byte[] bytes) - { - return ASCIIEncoding.ASCII.GetString(bytes); - } - - internal static byte[] GetBytesU(string st) - { - return ASCIIEncoding.Unicode.GetBytes(st); - } - - internal static string GetStringU(byte[] bytes) - { - return ASCIIEncoding.Unicode.GetString(bytes); - } - - internal static int UIThreadID { get; set; } - - internal static void DoSomethingInUIThread(Action action, bool blocking = false) - { - InvokeInFormThread(MainForm, UIThreadID, action, blocking); - } - - internal static int InputCallbackThreadID { get; set; } - - internal static void DoSomethingInTheInputCallbackThread(Action action, bool blocking = true) - { - InvokeInFormThread(InputCallbackForm, InputCallbackThreadID, action, blocking); - } - - private static void InvokeInFormThread(System.Windows.Forms.Form form, int threadId, Action action, bool blocking) - { - if (form != null) - { - int currentThreadId = Thread.CurrentThread.ManagedThreadId; - - if (currentThreadId == threadId) - { - action(); - } - else - { - bool done = false; - - try - { - Action callback = () => - { - try - { - action(); - } - catch (Exception e) - { - Logger.Log(e); - } - finally - { - done = true; - } - }; - _ = form.BeginInvoke(callback); - } - catch (Exception e) - { - done = true; - Logger.Log(e); - } - - while (blocking && !done) - { - Thread.Sleep(16); - - if (currentThreadId == UIThreadID || currentThreadId == InputCallbackThreadID) - { - Application.DoEvents(); - } - } - } - } - } - - private static readonly Lock InputSimulationLock = new(); - - internal static void DoSomethingInTheInputSimulationThread(ThreadStart target) - { - /* - * For some reason, SendInput may hit deadlock if it is called in the InputHookProc thread. - * For now leave it as is in the caller thread which is the socket receiver thread. - * */ - - // SendInput is thread-safe but few users seem to hit a deadlock occasionally, probably a Windows bug. - lock (InputSimulationLock) - { - target(); - } - } - - internal static void SendPackage(ID des, PackageType packageType) - { - DATA package = new(); - package.Type = packageType; - package.Des = des; - package.MachineName = MachineName; - - SkSend(package, null, false); - } - - internal static void SendHeartBeat(bool initial = false) - { - SendPackage(ID.ALL, initial && Encryption.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat); - } - - private static long lastSendNextMachine; - - internal static void SendNextMachine(ID hostMachine, ID nextMachine, Point requestedXY) - { - Logger.LogDebug($"SendNextMachine: Host machine: {hostMachine}, Next machine: {nextMachine}, Requested XY: {requestedXY}"); - - if (GetTick() - lastSendNextMachine < 100) - { - Logger.LogDebug("Machine switching in progress."); // "Move Mouse relatively" mode, slow machine/network, quick/busy hand. - return; - } - - lastSendNextMachine = GetTick(); - - DATA package = new(); - package.Type = PackageType.NextMachine; - - package.Des = hostMachine; - - package.Md.X = requestedXY.X; - package.Md.Y = requestedXY.Y; - package.Md.WheelDelta = (int)nextMachine; - - SkSend(package, null, false); - - Logger.LogDebug("SendNextMachine done."); - } - - private static ulong lastInputEventCount; - private static ulong lastRealInputEventCount; - - internal static void SendAwakeBeat() - { - if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() && - Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount) - { - SendPackage(ID.ALL, PackageType.Awake); - } - else - { - SendHeartBeat(); - } - - lastInputEventCount = Event.InputEventCount; - lastRealInputEventCount = Event.RealInputEventCount; - } - - internal static void HumanBeingDetected() - { - if (lastInputEventCount == Event.InputEventCount) - { - if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive()) - { - PokeMyself(); - } - } - - lastInputEventCount = Event.InputEventCount; - } - - internal static void PokeMyself() - { - int x, y = 0; - - for (int i = 0; i < 10; i++) - { - x = Encryption.Ran.Next(-9, 10); - InputSimulation.MoveMouseRelative(x, y); - Thread.Sleep(50); - InputSimulation.MoveMouseRelative(-x, -y); - Thread.Sleep(50); - - if (lastInputEventCount != Event.InputEventCount) - { - break; - } - } - } - - internal static void InitLastInputEventCount() - { - lastInputEventCount = Event.InputEventCount; - lastRealInputEventCount = Event.RealInputEventCount; - } - - internal static void SendHello() - { - SendPackage(ID.ALL, PackageType.Hello); - } - - /* - internal static void SendHi() - { - SendPackage(IP.ALL, PackageType.hi); - } - * */ - - internal static void SendByeBye() - { - Logger.LogDebug($"{nameof(SendByeBye)}"); - SendPackage(ID.ALL, PackageType.ByeBye); - } - - internal static void SendClipboardBeat() - { - SendPackage(ID.ALL, PackageType.Clipboard); - } - - internal static void ProcessByeByeMessage(DATA package) - { - if (package.Src == MachineStuff.desMachineID) - { - MachineStuff.SwitchToMachine(MachineName.Trim()); - } - - _ = MachineStuff.RemoveDeadMachines(package.Src); - } - - internal static long GetTick() // ms - { - return DateTime.Now.Ticks / 10000; - } - - internal static void SetToggleIcon(int[] toggleIcons) - { - Logger.LogDebug($"{nameof(SetToggleIcon)}: {toggleIcons?.FirstOrDefault()}"); - Common.toggleIcons = toggleIcons; - toggleIconsIndex = 0; - } - - internal static string CaptureScreen() - { - try - { - string fileName = GetMyStorageDir() + @"ScreenCaptureByMouseWithoutBorders.png"; - int w = MachineStuff.desktopBounds.Right - MachineStuff.desktopBounds.Left; - int h = MachineStuff.desktopBounds.Bottom - MachineStuff.desktopBounds.Top; - Bitmap bm = new(w, h); - Graphics g = Graphics.FromImage(bm); - Size s = new(w, h); - g.CopyFromScreen(MachineStuff.desktopBounds.Left, MachineStuff.desktopBounds.Top, 0, 0, s); - bm.Save(fileName, ImageFormat.Png); - bm.Dispose(); - return fileName; - } - catch (Exception e) - { - Logger.Log(e); - return null; - } - } - - internal static void PrepareScreenCapture() - { - Common.DoSomethingInUIThread(() => - { - if (!DragDrop.MouseDown && Helper.SendMessageToHelper(0x401, IntPtr.Zero, IntPtr.Zero) > 0) - { - Common.MMSleep(0.2); - InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT }); - InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)WM.LLKHF.UP, wVk = (int)VK.SNAPSHOT }); - - Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated."); - - _ = NativeMethods.MoveWindow( - (IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT), - MachineStuff.DesktopBounds.Left, - MachineStuff.DesktopBounds.Top, - MachineStuff.DesktopBounds.Right - MachineStuff.DesktopBounds.Left, - MachineStuff.DesktopBounds.Bottom - MachineStuff.DesktopBounds.Top, - false); - - _ = Helper.SendMessageToHelper(0x406, IntPtr.Zero, IntPtr.Zero, false); - } - else - { - Logger.Log("PrepareScreenCapture: Validation failed."); - } - }); - } - - internal static void OpenImage(string file) - { - // We want to run mspaint under the user account who ran explorer.exe (who logged in this current input desktop) - - // ImpersonateLoggedOnUserAndDoSomething(delegate() - // { - // Process.Start("explorer", "\"" + file + "\""); - // }); - _ = Launch.CreateProcessInInputDesktopSession( - "\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") + - "\"", - "\"" + file + "\"", - WinAPI.GetInputDesktop(), - 1); - - // CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") + - // " \"" + file + "\""); - - // We don't want to run mspaint as local system account - /* - ProcessStartInfo s = new ProcessStartInfo( - Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe"), - "\"" + file + "\""); - s.WindowStyle = ProcessWindowStyle.Maximized; - Process.Start(s); - * */ - } - - internal static void SendImage(string machine, string file) - { - Clipboard.LastDragDropFile = file; - - // Send ClipboardCapture - if (machine.Equals("All", StringComparison.OrdinalIgnoreCase)) - { - SendPackage(ID.ALL, PackageType.ClipboardCapture); - } - else - { - ID id = MachineStuff.MachinePool.ResolveID(machine); - if (id != ID.NONE) - { - SendPackage(id, PackageType.ClipboardCapture); - } - } - } - - internal static void SendImage(ID src, string file) - { - Clipboard.LastDragDropFile = file; - - // Send ClipboardCapture - SendPackage(src, PackageType.ClipboardCapture); - } - - internal static void ShowToolTip(string tip, int timeOutInMilliseconds = 5000, ToolTipIcon icon = ToolTipIcon.Info, bool showBalloonTip = true, bool forceEvenIfHidingOldUI = false) - { - if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) - { - DoSomethingInUIThread(() => - { - if (Setting.Values.FirstRun) - { - MachineStuff.Settings?.ShowTip(icon, tip, timeOutInMilliseconds); - } - - Common.MatrixForm?.ShowTip(icon, tip, timeOutInMilliseconds); - - if (showBalloonTip) - { - if (MainForm != null) - { - MainForm.ShowToolTip(tip, timeOutInMilliseconds, forceEvenIfHidingOldUI: forceEvenIfHidingOldUI); - } - else - { - Logger.Log(tip); - } - } - }); - } - } - - private static FrmMessage topMostMessageForm; - - internal static void ToggleShowTopMostMessage(string text, string bigText, int timeOut) - { - DoSomethingInUIThread(() => - { - if (topMostMessageForm == null) - { - topMostMessageForm = new FrmMessage(text, bigText, timeOut); - topMostMessageForm.Show(); - } - else - { - FrmMessage currentMessageForm = topMostMessageForm; - topMostMessageForm = null; - currentMessageForm.Close(); - } - }); - } - - internal static void HideTopMostMessage() - { - DoSomethingInUIThread(() => - { - topMostMessageForm?.Close(); - }); - } - - internal static void NullTopMostMessage() - { - DoSomethingInUIThread(() => - { - if (topMostMessageForm != null) - { - topMostMessageForm = null; - } - }); - } - - internal static bool IsTopMostMessageNotNull() - { - return topMostMessageForm != null; - } - - private static bool TestSend(TcpSk t) - { - ID remoteMachineID; - - if (t.Status == SocketStatus.Connected) - { - try - { - DATA package = new(); - package.Type = PackageType.Hi; - package.Des = remoteMachineID = (ID)t.MachineId; - package.MachineName = MachineName; - - _ = Sk.TcpSend(t, package); - t.EncryptedStream?.Flush(); - - return true; - } - catch (ExpectedSocketException) - { - t.BackingSocket = null; // To be removed at CloseAnUnusedSocket() - } - } - - t.Status = SocketStatus.SendError; - return false; - } - - internal static bool IsConnectedTo(ID remoteMachineID) - { - bool updateClientSockets = false; - - if (remoteMachineID == MachineID) - { - return true; - } - - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - if (sk.TcpSockets != null) - { - foreach (TcpSk t in sk.TcpSockets) - { - if (t.Status == SocketStatus.Connected && (uint)remoteMachineID == t.MachineId) - { - if (TestSend(t)) - { - return true; - } - else - { - updateClientSockets = true; - } - } - } - } - } - } - - if (updateClientSockets) - { - MachineStuff.UpdateClientSockets(nameof(IsConnectedTo)); - } - - return false; - } - -#if DEBUG - private static long minSendTime = long.MaxValue; - private static long avgSendTime; - private static long maxSendTime; - private static long totalSendCount; - private static long totalSendTime; -#endif - - internal static void SkSend(DATA data, uint? exceptDes, bool includeHandShakingSockets) - { - bool connected = false; - - SocketStuff sk = Sk; - - if (sk != null) - { -#if DEBUG - long startStop = DateTime.Now.Ticks; - totalSendCount++; -#endif - - try - { - data.Id = Interlocked.Increment(ref Package.PackageID); - - bool updateClientSockets = false; - - lock (sk.TcpSocketsLock) - { - foreach (TcpSk t in sk.TcpSockets) - { - if (t != null && t.BackingSocket != null && (t.Status == SocketStatus.Connected || (t.Status == SocketStatus.Handshaking && includeHandShakingSockets))) - { - if (t.MachineId == (uint)data.Des || (data.Des == ID.ALL && t.MachineId != exceptDes && MachineStuff.InMachineMatrix(t.MachineName))) - { - try - { - sk.TcpSend(t, data); - - if (data.Des != ID.ALL) - { - connected = true; - } - } - catch (ExpectedSocketException) - { - t.BackingSocket = null; // To be removed at CloseAnUnusedSocket() - updateClientSockets = true; - } - catch (Exception e) - { - Logger.Log(e); - t.BackingSocket = null; // To be removed at CloseAnUnusedSocket() - updateClientSockets = true; - } - } - } - } - } - - if (!connected && data.Des != ID.ALL) - { - Logger.LogDebug("********** No active connection found for the remote machine! **********" + data.Des.ToString()); - - if (data.Des == ID.NONE || MachineStuff.RemoveDeadMachines(data.Des)) - { - // SwitchToMachine(MachineName.Trim()); - MachineStuff.NewDesMachineID = DesMachineID = MachineID; - MachineStuff.SwitchLocation.X = Event.XY_BY_PIXEL + Event.myLastX; - MachineStuff.SwitchLocation.Y = Event.XY_BY_PIXEL + Event.myLastY; - MachineStuff.SwitchLocation.ResetCount(); - EvSwitch.Set(); - } - } - - if (updateClientSockets) - { - MachineStuff.UpdateClientSockets("SkSend"); - } - } - catch (Exception e) - { - Logger.Log(e); - } - -#if DEBUG - startStop = DateTime.Now.Ticks - startStop; - totalSendTime += startStop; - if (startStop < minSendTime) - { - minSendTime = startStop; - } - - if (startStop > maxSendTime) - { - maxSendTime = startStop; - } - - avgSendTime = totalSendTime / totalSendCount; -#endif - } - else - { - Package.PackageSent.Nil++; - } - } - - internal static void CloseAnUnusedSocket() - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - if (sk.TcpSockets != null) - { - TcpSk tobeRemoved = null; - - foreach (TcpSk t in sk.TcpSockets) - { - if ((t.Status != SocketStatus.Connected && t.BirthTime < GetTick() - SocketStuff.CONNECT_TIMEOUT) || t.BackingSocket == null) - { - Logger.LogDebug("CloseAnUnusedSocket: " + t.MachineName + ":" + t.MachineId + "|" + t.Status.ToString()); - tobeRemoved = t; - - if (t.BackingSocket != null) - { - try - { - t.BackingSocket.Close(); - } - catch (Exception e) - { - Logger.Log(e); - } - } - - break; // Each time we try to remove one socket only. - } - } - - if (tobeRemoved != null) - { - _ = sk.TcpSockets.Remove(tobeRemoved); - } - } - } - } - } - - internal static bool AtLeastOneSocketConnected() - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - if (sk.TcpSockets != null) - { - foreach (TcpSk t in sk.TcpSockets) - { - if (t.Status == SocketStatus.Connected) - { - Logger.LogDebug("AtLeastOneSocketConnected returning true: " + t.MachineName); - return true; - } - } - } - } - } - - Logger.LogDebug("AtLeastOneSocketConnected returning false."); - return false; - } - - internal static Socket AtLeastOneServerSocketConnected() - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - if (sk.TcpSockets != null) - { - foreach (TcpSk t in sk.TcpSockets) - { - if (!t.IsClient && t.Status == SocketStatus.Connected) - { - Logger.LogDebug("AtLeastOneServerSocketConnected returning true: " + t.MachineName); - return t.BackingSocket; - } - } - } - } - } - - Logger.LogDebug("AtLeastOneServerSocketConnected returning false."); - return null; - } - - internal static TcpSk GetConnectedClientSocket() - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - return sk.TcpSockets?.FirstOrDefault(item => item.IsClient && item.Status == SocketStatus.Connected); - } - } - else - { - return null; - } - } - - internal static bool AtLeastOneSocketEstablished() - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - if (sk.TcpSockets != null) - { - foreach (TcpSk t in sk.TcpSockets) - { - if (t.BackingSocket != null && t.BackingSocket.Connected) - { - if (TestSend(t)) - { - Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning true: {t.MachineName}"); - return true; - } - } - } - } - } - } - - Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning false."); - return false; - } - - internal static bool IsConnectedByAClientSocketTo(string machineName) - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - foreach (TcpSk t in sk.TcpSockets) - { - if (t != null && t.IsClient && t.Status == SocketStatus.Connected - && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - } - } - - return false; - } - - internal static IPAddress GetConnectedClientSocketIPAddressFor(string machineName) - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - return sk.TcpSockets.FirstOrDefault(t => t != null && t.IsClient && t.Status == SocketStatus.Connected - && t.Address != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase)) - ?.Address; - } - } - - return null; - } - - internal static bool IsConnectingByAClientSocketTo(string machineName, IPAddress ip) - { - SocketStuff sk = Common.Sk; - - if (sk != null) - { - lock (sk.TcpSocketsLock) - { - foreach (TcpSk t in sk.TcpSockets) - { - if (t != null && t.IsClient && t.Status == SocketStatus.Connecting - && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase) - && t.Address.ToString().Equals(ip.ToString(), StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - } - } - - return false; - } - - internal static void UpdateSetupMachineMatrix(string desMachine) - { - int machineCt = 0; - - foreach (string m in MachineStuff.MachineMatrix) - { - if (!string.IsNullOrEmpty(m.Trim())) - { - machineCt++; - } - } - - if (machineCt < 2 && MachineStuff.Settings != null && (MachineStuff.Settings.GetCurrentPage() is SetupPage1 || MachineStuff.Settings.GetCurrentPage() is SetupPage2b)) - { - MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { Common.MachineName.Trim(), desMachine, string.Empty, string.Empty }; - Logger.LogDebug("UpdateSetupMachineMatrix: " + string.Join(",", MachineStuff.MachineMatrix)); - - Common.DoSomethingInUIThread( - () => - { - MachineStuff.Settings.SetControlPage(new SetupPage4()); - }, - true); - } - } - - internal static void ReopenSockets(bool byUser) - { - DoSomethingInUIThread( - () => - { - try - { - SocketStuff tmpSk = Sk; - - if (tmpSk != null) - { - Sk = null; // TODO: This looks redundant. - tmpSk.Close(byUser); - } - - Sk = new SocketStuff(tcpPort, byUser); - } - catch (Exception e) - { - Sk = null; - Logger.Log(e); - } - - if (Sk != null) - { - if (byUser) - { - SocketStuff.ClearBadIPs(); - } - - MachineStuff.UpdateClientSockets("ReopenSockets"); - } - }, - true); - - if (Sk == null) - { - return; - } - - Common.DoSomethingInTheInputCallbackThread(() => - { - if (Common.Hook != null) - { - Common.Hook.Stop(); - Common.Hook = null; - } - - if (byUser) - { - Common.InputCallbackForm.Close(); - Common.InputCallbackForm = null; - Program.StartInputCallbackThread(); - } - else - { - Common.InputCallbackForm.InstallKeyboardAndMouseHook(); - } - }); - } - - internal static string GetMyStorageDir() - { - string st = string.Empty; - - try - { - if (RunOnLogonDesktop || RunOnScrSaverDesktop) - { - st = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); - if (!Directory.Exists(st)) - { - _ = Directory.CreateDirectory(st); - } - - st += @"\" + Common.BinaryName; - if (!Directory.Exists(st)) - { - _ = Directory.CreateDirectory(st); - } - - st += @"\ScreenCaptures\"; - if (!Directory.Exists(st)) - { - _ = Directory.CreateDirectory(st); - } - } - else - { - _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => - { - st = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\" + Common.BinaryName; - if (!Directory.Exists(st)) - { - _ = Directory.CreateDirectory(st); - } - - st += @"\ScreenCaptures\"; - if (!Directory.Exists(st)) - { - _ = Directory.CreateDirectory(st); - } - }); - } - - Logger.LogDebug("GetMyStorageDir: " + st); - - // Delete old files. - foreach (FileInfo fi in new DirectoryInfo(st).GetFiles()) - { - if (fi.CreationTime.AddDays(1) < DateTime.Now) - { - fi.Delete(); - } - } - - return st; - } - catch (Exception e) - { - Logger.Log(e); - - if (string.IsNullOrEmpty(st) || !st.Contains(Common.BinaryName)) - { - st = Path.GetTempPath(); - } - - return st; - } - } - - internal static void GetMachineName() - { - string machine_Name = string.Empty; - - try - { - machine_Name = Dns.GetHostName(); - Logger.LogDebug("GetHostName = " + machine_Name); - } - catch (Exception e) - { - Logger.Log(e); - - if (string.IsNullOrEmpty(machine_Name)) - { - machine_Name = "RANDOM" + Encryption.Ran.Next().ToString(CultureInfo.CurrentCulture); - } - } - - if (machine_Name.Length > 32) - { - machine_Name = machine_Name[..32]; - } - - Common.MachineName = machine_Name.Trim(); - - Logger.LogDebug($"========== {nameof(GetMachineName)} ended!"); - } - - private static string GetNetworkName(NetworkInterface networkInterface) - { - return $"{networkInterface.Name} | {networkInterface.Description.Replace(":", "-")}"; - } - - internal static string GetRemoteStringIP(Socket s, bool throwException = false) - { - if (s == null) - { - return string.Empty; - } - - string ip; - - try - { - ip = (s?.RemoteEndPoint as IPEndPoint)?.Address?.ToString(); - - if (string.IsNullOrEmpty(ip)) - { - return string.Empty; - } - } - catch (ObjectDisposedException e) - { - Logger.Log($"{nameof(GetRemoteStringIP)}: The socket could have been disposed by other threads, error: {e.Message}"); - - if (throwException) - { - throw; - } - - return string.Empty; - } - catch (SocketException e) - { - Logger.Log($"{nameof(GetRemoteStringIP)}: {e.Message}"); - - if (throwException) - { - throw; - } - - return string.Empty; - } - - return ip; - } - - internal static void CloseAllFormsAndHooks() - { - if (Hook != null) - { - Hook.Stop(); - Hook = null; - if (InputCallbackForm != null) - { - DoSomethingInTheInputCallbackThread(() => - { - InputCallbackForm.Close(); - InputCallbackForm = null; - }); - } - } - - if (MainForm != null) - { - MainForm.Destroy(); - MainForm = null; - } - - if (MatrixForm != null) - { - MatrixForm.Close(); - MatrixForm = null; - } - - if (AboutForm != null) - { - AboutForm.Close(); - AboutForm = null; - } - } - - internal static void MoveMouseToCenter() - { - Logger.LogDebug("+++++ MoveMouseToCenter"); - InputSimulation.MoveMouse( - MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2), - MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2)); - } - - internal static void HideMouseCursor(bool byHideMouseMessage) - { - Common.LastPos = new Point( - MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2), - Setting.Values.HideMouse ? 4 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2)); - - if ((MachineStuff.desMachineID != MachineID && MachineStuff.desMachineID != ID.ALL) || byHideMouseMessage) - { - _ = NativeMethods.SetCursorPos(Common.LastPos.X, Common.LastPos.Y); - _ = NativeMethods.GetCursorPos(ref Common.lastPos); - Logger.LogDebug($"+++++ HideMouseCursor, byHideMouseMessage = {byHideMouseMessage}"); - } - - CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue); - } - - internal static string GetText(IntPtr hWnd) - { - int length = NativeMethods.GetWindowTextLength(hWnd); - StringBuilder sb = new(length + 1); - int rv = NativeMethods.GetWindowText(hWnd, sb, sb.Capacity); - Logger.LogDebug("GetWindowText returned " + rv.ToString(CultureInfo.CurrentCulture)); - return sb.ToString(); - } - - public static string GetWindowClassName(IntPtr hWnd) - { - StringBuilder buffer = new(128); - _ = NativeMethods.GetClassName(hWnd, buffer, buffer.Capacity); - return buffer.ToString(); - } - - internal static void MMSleep(double secs) - { - for (int i = 0; i < secs * 10; i++) - { - Application.DoEvents(); - Thread.Sleep(100); - } - } - - internal static void UpdateMultipleModeIconAndMenu() - { - MainForm?.UpdateMultipleModeIconAndMenu(); - } - - internal static void SendOrReceiveARandomDataBlockPerInitialIV(Stream st, bool send = true) - { - byte[] ranData = new byte[Encryption.SymAlBlockSize]; - - try - { - if (send) - { - ranData = RandomNumberGenerator.GetBytes(Encryption.SymAlBlockSize); - st.Write(ranData, 0, ranData.Length); - } - else - { - int toRead = ranData.Length; - int read = st.ReadEx(ranData, 0, toRead); - - if (read != toRead) - { - Logger.LogDebug("Stream has no more data after reading {0} bytes.", read); - } - } - } - catch (IOException e) - { - string log = $"{nameof(SendOrReceiveARandomDataBlockPerInitialIV)}: Exception {(send ? "writing" : "reading")} to the socket stream: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)"; - Logger.Log(log); - - if (e.InnerException is not (SocketException or ObjectDisposedException)) - { - throw; - } - } - } - - private static bool DisableEasyMouseWhenForegroundWindowIsFullscreenSetting() - { - return Setting.Values.DisableEasyMouseWhenForegroundWindowIsFullscreen; - } - - private static bool IsAppIgnoredByEasyMouseFullscreenCheck(IntPtr foregroundWindowHandle) - { - if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out var processId) == 0) - { - Logger.LogDebug($"GetWindowThreadProcessId failed with error : {Marshal.GetLastWin32Error()}"); - return false; - } - - var processHandle = NativeMethods.OpenProcess(0x1000, false, processId); - if (processHandle == IntPtr.Zero) - { - return false; - } - - uint maxPath = 260; - var nameBuffer = new char[maxPath]; - if (!NativeMethods.QueryFullProcessImageName( - processHandle, NativeMethods.QUERY_FULL_PROCESS_NAME_FLAGS.DEFAULT, nameBuffer, ref maxPath)) - { - Logger.LogDebug($"QueryFullProcessImageName failed with error : {Marshal.GetLastWin32Error()}"); - NativeMethods.CloseHandle(processHandle); - return false; - } - - NativeMethods.CloseHandle(processHandle); - - var name = new string(nameBuffer, 0, (int)maxPath); - - var excludedApps = Setting.Values.EasyMouseFullscreenSwitchBlockExcludedApps; - - return excludedApps.Contains(Path.GetFileNameWithoutExtension(name), StringComparer.OrdinalIgnoreCase) - || excludedApps.Contains(Path.GetFileName(name), StringComparer.OrdinalIgnoreCase); - } - - internal static bool IsEasyMouseBlockedByFullscreenWindow() - { - var shellHandle = NativeMethods.GetShellWindow(); - var desktopHandle = NativeMethods.GetDesktopWindow(); - var foregroundHandle = NativeMethods.GetForegroundWindow(); - - // If the foreground window is either the desktop or the Windows shell, we are not in fullscreen mode. - if (foregroundHandle.Equals(shellHandle) || foregroundHandle.Equals(desktopHandle)) - { - return false; - } - - if (NativeMethods.SHQueryUserNotificationState(out var userNotificationState) != 0) - { - Logger.LogDebug($"SHQueryUserNotificationState failed with error : {Marshal.GetLastWin32Error()}"); - return false; - } - - switch (userNotificationState) - { - // An application running in full screen mode, check if the foreground window is - // listed as ignored in the settings. - case NativeMethods.USER_NOTIFICATION_STATE.BUSY: - case NativeMethods.USER_NOTIFICATION_STATE.RUNNING_D3D_FULL_SCREEN: - case NativeMethods.USER_NOTIFICATION_STATE.PRESENTATION_MODE: - return !IsAppIgnoredByEasyMouseFullscreenCheck(foregroundHandle); - - // No full screen app running. - case NativeMethods.USER_NOTIFICATION_STATE.NOT_PRESENT: - case NativeMethods.USER_NOTIFICATION_STATE.ACCEPTS_NOTIFICATIONS: - case NativeMethods.USER_NOTIFICATION_STATE.QUIET_TIME: - // Cannot determine - case NativeMethods.USER_NOTIFICATION_STATE.APP: - default: - return false; - } - } - - /// - /// Check if a machine switch triggered by EasyMouse would be allowed to proceed due to other settings. - /// - /// A boolean that tells us if the switch isn't blocked by any other settings - internal static bool IsEasyMouseSwitchAllowed() - { - // Never prevent a switch if we are not moving out of the host machine. - if (!DisableEasyMouseWhenForegroundWindowIsFullscreenSetting() || DesMachineID != MachineID) - { - return true; - } - - // Check if the switch is blocked by a full-screen window running in the foreground - return !IsEasyMouseBlockedByFullscreenWindow(); - } - } -} diff --git a/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs b/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs index 62360b4795..15ac6fb8b8 100644 --- a/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs +++ b/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs @@ -18,12 +18,12 @@ using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.VisualStudio.Threading; +using MouseWithoutBorders.Core; using Newtonsoft.Json; using StreamJsonRpc; #if !MM_HELPER using MouseWithoutBorders.Class; -using MouseWithoutBorders.Core; #endif using SystemClipboard = System.Windows.Forms.Clipboard; @@ -246,11 +246,11 @@ WellKnownSidType.AuthenticatedUserSid, null); CancellationToken cancellationToken = _serverTaskCancellationSource.Token; IpcChannel.StartIpcServer(ChannelName + "/" + RemoteObjectName, cancellationToken); - Common.IpcChannelCreated = true; + IpcChannelHelper.IpcChannelCreated = true; } catch (Exception e) { - Common.IpcChannelCreated = false; + IpcChannelHelper.IpcChannelCreated = false; Common.ShowToolTip("Error setting up clipboard sharing, clipboard sharing will not work!", 5000, ToolTipIcon.Error); Logger.Log(e); } @@ -405,7 +405,7 @@ WellKnownSidType.AuthenticatedUserSid, null); try { - rv = Common.Retry(nameof(SystemClipboard.ContainsFileDropList), () => { return SystemClipboard.ContainsFileDropList(); }, (log) => Log(log)); + rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsFileDropList), () => { return SystemClipboard.ContainsFileDropList(); }, (log) => Log(log)); } catch (ExternalException e) { @@ -427,7 +427,7 @@ WellKnownSidType.AuthenticatedUserSid, null); try { - rv = Common.Retry(nameof(SystemClipboard.ContainsImage), () => { return SystemClipboard.ContainsImage(); }, (log) => Log(log)); + rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsImage), () => { return SystemClipboard.ContainsImage(); }, (log) => Log(log)); } catch (ExternalException e) { @@ -449,7 +449,7 @@ WellKnownSidType.AuthenticatedUserSid, null); try { - rv = Common.Retry(nameof(SystemClipboard.ContainsText), () => { return SystemClipboard.ContainsText(); }, (log) => Log(log)); + rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsText), () => { return SystemClipboard.ContainsText(); }, (log) => Log(log)); } catch (ExternalException e) { @@ -471,7 +471,7 @@ WellKnownSidType.AuthenticatedUserSid, null); try { - rv = Common.Retry(nameof(SystemClipboard.GetFileDropList), () => { return SystemClipboard.GetFileDropList(); }, (log) => Log(log)); + rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetFileDropList), () => { return SystemClipboard.GetFileDropList(); }, (log) => Log(log)); } catch (ExternalException e) { @@ -493,7 +493,7 @@ WellKnownSidType.AuthenticatedUserSid, null); try { - rv = Common.Retry(nameof(SystemClipboard.GetImage), () => { return SystemClipboard.GetImage(); }, (log) => Log(log)); + rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetImage), () => { return SystemClipboard.GetImage(); }, (log) => Log(log)); } catch (ExternalException e) { @@ -515,7 +515,7 @@ WellKnownSidType.AuthenticatedUserSid, null); try { - rv = Common.Retry(nameof(SystemClipboard.GetText), () => { return SystemClipboard.GetText(format); }, (log) => Log(log)); + rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetText), () => { return SystemClipboard.GetText(format); }, (log) => Log(log)); } catch (ExternalException e) { @@ -539,7 +539,7 @@ WellKnownSidType.AuthenticatedUserSid, null); { try { - _ = Common.Retry( + _ = IpcChannelHelper.Retry( nameof(SystemClipboard.SetImage), () => { @@ -568,7 +568,7 @@ WellKnownSidType.AuthenticatedUserSid, null); { try { - _ = Common.Retry( + _ = IpcChannelHelper.Retry( nameof(SystemClipboard.SetText), () => { @@ -600,44 +600,4 @@ WellKnownSidType.AuthenticatedUserSid, null); { internal const int QUIT_CMD = 0x409; } - - internal sealed partial class Common - { - internal static bool IpcChannelCreated { get; set; } - - internal static T Retry(string name, Func func, Action log, Action preRetry = null) - { - int count = 0; - - do - { - try - { - T rv = func(); - - if (count > 0) - { - log($"Trace: {name} has been successful after {count} retry."); - } - - return rv; - } - catch (Exception) - { - count++; - - preRetry?.Invoke(); - - if (count > 10) - { - throw; - } - - Application.DoEvents(); - Thread.Sleep(200); - } - } - while (true); - } - } } diff --git a/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs index e557ff4a37..db16ac8b4d 100644 --- a/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs +++ b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs @@ -1036,7 +1036,7 @@ internal static class Clipboard { try { - _ = Common.Retry( + _ = IpcChannelHelper.Retry( nameof(SystemClipboard.SetFileDropList), () => { @@ -1073,7 +1073,7 @@ internal static class Clipboard { try { - _ = Common.Retry( + _ = IpcChannelHelper.Retry( nameof(SystemClipboard.SetImage), () => { @@ -1104,7 +1104,7 @@ internal static class Clipboard { try { - _ = Common.Retry( + _ = IpcChannelHelper.Retry( nameof(SystemClipboard.SetText), () => { diff --git a/src/modules/MouseWithoutBorders/App/Core/Common.cs b/src/modules/MouseWithoutBorders/App/Core/Common.cs new file mode 100644 index 0000000000..3c2206f2ab --- /dev/null +++ b/src/modules/MouseWithoutBorders/App/Core/Common.cs @@ -0,0 +1,1654 @@ +// 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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +using Microsoft.PowerToys.Settings.UI.Library; +using MouseWithoutBorders.Class; +using MouseWithoutBorders.Exceptions; + +using Clipboard = MouseWithoutBorders.Core.Clipboard; +using Thread = MouseWithoutBorders.Core.Thread; + +// Log is enough +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckClipboard()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetAsStartupItem(System.Boolean)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HelperThread()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetMyStorageDir()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#MouseEvent(MouseWithoutBorders.MOUSEDATA)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#KeybdEvent(MouseWithoutBorders.KEYBDDATA)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartMouseWithoutBordersService()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HookClipboard()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardData(MouseWithoutBorders.DATA,System.Boolean)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiverCallback(System.Object)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckNewVersion()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartServiceAndSendLogoffSignal()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetScreenConfig()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CaptureScreen()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#InitEncryption()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ToggleIcon()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNameAndIPAddresses()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#Cleanup()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "MouseWithoutBorders.Common", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ProcessPackage(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#get_Machine_Pool()", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean,System.String)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNewImageAndSaveTo(System.String,System.String)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#LogAll()", MessageId = "System.String.Format(System.IFormatProvider,System.String,System.Object[])", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#DragDropStep04()", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", MessageId = "MouseWithoutBorders.NativeMethods.WaitForSingleObject(System.IntPtr,System.Int32)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#GetText(System.IntPtr)", MessageId = "MouseWithoutBorders.NativeMethods.GetWindowText(System.IntPtr,System.Text.StringBuilder,System.Int32)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", MessageId = "MouseWithoutBorders.NativeMethods.WTSQueryUserToken(System.UInt32,System.IntPtr@)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32,System.Boolean,System.Int64)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateProcessInInputDesktopSession(System.String,System.String,System.String,System.Boolean,System.Int16)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SkSend(MouseWithoutBorders.DATA,System.Boolean,System.Int32)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardDataUsingTCP(MouseWithoutBorders.DATA,System.Boolean,System.Net.Sockets.Socket)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#UpdateMachineMatrix(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")] +[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReopenSockets(System.Boolean)", Justification = "Dotnet port with style preservation")] + +// +// Most of the helper methods. +// +// +// 2008 created by Truong Do (ductdo). +// 2009-... modified by Truong Do (TruongDo). +// 2023- Included in PowerToys. +// +namespace MouseWithoutBorders.Core; + +internal static class Common +{ + private static InputHook hook; + private static FrmMatrix matrixForm; + private static FrmInputCallback inputCallbackForm; + private static FrmAbout aboutForm; +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter + internal static Thread helper; + internal static int screenWidth; + internal static int screenHeight; +#pragma warning restore SA1307 + private static int lastX; + private static int lastY; + + private static bool mainFormVisible = true; + private static bool runOnLogonDesktop; + private static bool runOnScrSaverDesktop; + +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter + internal static int[] toggleIcons; + internal static int toggleIconsIndex; +#pragma warning restore SA1307 + internal const int TOGGLE_ICONS_SIZE = 4; + internal const int ICON_ONE = 0; + internal const int ICON_ALL = 1; + internal const int ICON_SMALL_CLIPBOARD = 2; + internal const int ICON_BIG_CLIPBOARD = 3; + internal const int ICON_ERROR = 4; + internal const int JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999; + + internal const int NETWORK_STREAM_BUF_SIZE = 1024 * 1024; + internal static readonly EventWaitHandle EvSwitch = new(false, EventResetMode.AutoReset); + private static Point lastPos; +#pragma warning disable SA1307 // Accessible fields should begin with upper-case names + internal static int switchCount; +#pragma warning restore SA1307 + private static long lastReconnectByHotKeyTime; +#pragma warning disable SA1307 // Accessible fields should begin with upper-case names + internal static int tcpPort; +#pragma warning restore SA1307 + private static bool secondOpenSocketTry; + private static string binaryName; + + internal static Process CurrentProcess { get; set; } + + internal static bool HotkeyMatched(int vkCode, bool winDown, bool ctrlDown, bool altDown, bool shiftDown, HotkeySettings hotkey) + { + return !hotkey.IsEmpty() && (vkCode == hotkey.Code) && (!hotkey.Win || winDown) && (!hotkey.Alt || altDown) && (!hotkey.Shift || shiftDown) && (!hotkey.Ctrl || ctrlDown); + } + + internal static string BinaryName + { + get => Common.binaryName; + set => Common.binaryName = value; + } + + internal static bool SecondOpenSocketTry + { + get => Common.secondOpenSocketTry; + set => Common.secondOpenSocketTry = value; + } + + internal static long LastReconnectByHotKeyTime + { + get => Common.lastReconnectByHotKeyTime; + set => Common.lastReconnectByHotKeyTime = value; + } + + internal static int SwitchCount + { + get => Common.switchCount; + set => Common.switchCount = value; + } + + internal static Point LastPos + { + get => Common.lastPos; + set => Common.lastPos = value; + } + + internal static FrmAbout AboutForm + { + get => Common.aboutForm; + set => Common.aboutForm = value; + } + + internal static FrmInputCallback InputCallbackForm + { + get => Common.inputCallbackForm; + set => Common.inputCallbackForm = value; + } + + internal static int PaintCount { get; set; } + + internal static bool RunOnScrSaverDesktop + { + get => Common.runOnScrSaverDesktop; + set => Common.runOnScrSaverDesktop = value; + } + + internal static bool RunOnLogonDesktop + { + get => Common.runOnLogonDesktop; + set => Common.runOnLogonDesktop = value; + } + + internal static bool RunWithNoAdminRight { get; set; } + + internal static int LastX + { + get => Common.lastX; + set => Common.lastX = value; + } + + internal static int LastY + { + get => Common.lastY; + set => Common.lastY = value; + } + + internal static int[] ToggleIcons => Common.toggleIcons; + + internal static int ScreenHeight => Common.screenHeight; + + internal static int ScreenWidth => Common.screenWidth; + + internal static bool Is64bitOS + { + get; set; + + // set { Common.is64bitOS = value; } + } + + internal static int ToggleIconsIndex + { + // get { return Common.toggleIconsIndex; } + set => Common.toggleIconsIndex = value; + } + + internal static InputHook Hook + { + get => Common.hook; + set => Common.hook = value; + } + + internal static SocketStuff Sk { get; set; } + + internal static FrmScreen MainForm { get; set; } + + internal static FrmMouseCursor MouseCursorForm { get; set; } + + internal static FrmMatrix MatrixForm + { + get => Common.matrixForm; + set => Common.matrixForm = value; + } + + internal static ID DesMachineID + { + get => MachineStuff.desMachineID; + + set + { + MachineStuff.desMachineID = value; + MachineStuff.DesMachineName = MachineStuff.NameFromID(MachineStuff.desMachineID); + } + } + + internal static ID MachineID => (ID)Setting.Values.MachineId; + + internal static string MachineName { get; set; } + + internal static bool MainFormVisible + { + get => Common.mainFormVisible; + set => Common.mainFormVisible = value; + } + + internal static Mutex SocketMutex { get; set; } // Synchronization between MouseWithoutBorders running in different desktops + + // TODO: For telemetry only, to be removed. + private static int socketMutexBalance; + + internal static void ReleaseSocketMutex() + { + if (SocketMutex != null) + { + Logger.LogDebug("SOCKET MUTEX BEGIN RELEASE."); + + try + { + _ = Interlocked.Decrement(ref socketMutexBalance); + SocketMutex.ReleaseMutex(); + } + catch (ApplicationException e) + { + // The current thread does not own the mutex, the thread acquired it will own it. + Logger.TelemetryLogTrace($"{nameof(ReleaseSocketMutex)}: {e.Message}. {Thread.CurrentThread.ManagedThreadId}/{UIThreadID}.", SeverityLevel.Warning); + } + + Logger.LogDebug("SOCKET MUTEX RELEASED."); + } + else + { + Logger.LogDebug("SOCKET MUTEX NULL."); + } + } + + internal static void AcquireSocketMutex() + { + if (SocketMutex != null) + { + Logger.LogDebug("SOCKET MUTEX BEGIN WAIT."); + int waitTimeout = 60000; // TcpListener.Stop may take very long to complete for some reason. + + int socketMutexBalance = int.MinValue; + + bool acquireMutex = ExecuteAndTrace( + "Waiting for sockets to close", + () => + { + socketMutexBalance = Interlocked.Increment(ref Common.socketMutexBalance); + _ = SocketMutex.WaitOne(waitTimeout); // The app now requires .Net 4.0. Note: .Net20RTM does not have the one-parameter version of the API. + }, + TimeSpan.FromSeconds(5)); + + // Took longer than expected. + if (!acquireMutex) + { + Process[] ps = Process.GetProcessesByName(Common.BinaryName); + Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {WinAPI.IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {WinAPI.GetMyDesktop()}/{WinAPI.GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning); + } + + Logger.LogDebug("SOCKET MUTEX ENDED."); + } + else + { + Logger.LogDebug("SOCKET MUTEX NULL."); + } + } + + internal static bool BlockingUI { get; private set; } + + internal static bool ExecuteAndTrace(string actionName, Action action, TimeSpan timeout, bool restart = false) + { + bool rv = true; + Logger.LogDebug(actionName); + bool done = false; + + BlockingUI = true; + + if (restart) + { + Common.MainForm.Text = Setting.Values.MyIdEx; + + /* closesocket() rarely gets stuck for some reason inside ntdll!ZwClose ...=>... afd!AfdCleanupCore. + * There is no good workaround for it so far, still working with [Winsock 2.0 Discussions] to address the issue. + * */ + new Thread( + () => + { + for (int i = 0; i < timeout.TotalSeconds; i++) + { + Thread.Sleep(1000); + + if (done) + { + return; + } + } + + Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}, restarting the process.", SeverityLevel.Warning, true); + + string desktop = WinAPI.GetMyDesktop(); + MachineStuff.oneInstanceCheck?.Close(); + _ = Process.Start(Application.ExecutablePath, desktop); + Logger.LogDebug($"Started on desktop {desktop}"); + + Process.GetCurrentProcess().KillProcess(true); + }, + $"{actionName} watchdog").Start(); + } + + Stopwatch timer = Stopwatch.StartNew(); + + try + { + action(); + } + finally + { + done = true; + BlockingUI = false; + + if (restart) + { + Common.MainForm.Text = Setting.Values.MyID; + } + + timer.Stop(); + + if (timer.Elapsed > timeout) + { + rv = false; + + if (!restart) + { + Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}: {(long)timer.Elapsed.TotalSeconds}.", SeverityLevel.Warning); + } + } + } + + return rv; + } + + internal static byte[] GetBytes(string st) + { + return ASCIIEncoding.ASCII.GetBytes(st); + } + + internal static string GetString(byte[] bytes) + { + return ASCIIEncoding.ASCII.GetString(bytes); + } + + internal static byte[] GetBytesU(string st) + { + return ASCIIEncoding.Unicode.GetBytes(st); + } + + internal static string GetStringU(byte[] bytes) + { + return ASCIIEncoding.Unicode.GetString(bytes); + } + + internal static int UIThreadID { get; set; } + + internal static void DoSomethingInUIThread(Action action, bool blocking = false) + { + InvokeInFormThread(MainForm, UIThreadID, action, blocking); + } + + internal static int InputCallbackThreadID { get; set; } + + internal static void DoSomethingInTheInputCallbackThread(Action action, bool blocking = true) + { + InvokeInFormThread(InputCallbackForm, InputCallbackThreadID, action, blocking); + } + + private static void InvokeInFormThread(System.Windows.Forms.Form form, int threadId, Action action, bool blocking) + { + if (form != null) + { + int currentThreadId = Thread.CurrentThread.ManagedThreadId; + + if (currentThreadId == threadId) + { + action(); + } + else + { + bool done = false; + + try + { + Action callback = () => + { + try + { + action(); + } + catch (Exception e) + { + Logger.Log(e); + } + finally + { + done = true; + } + }; + _ = form.BeginInvoke(callback); + } + catch (Exception e) + { + done = true; + Logger.Log(e); + } + + while (blocking && !done) + { + Thread.Sleep(16); + + if (currentThreadId == UIThreadID || currentThreadId == InputCallbackThreadID) + { + Application.DoEvents(); + } + } + } + } + } + + private static readonly Lock InputSimulationLock = new(); + + internal static void DoSomethingInTheInputSimulationThread(ThreadStart target) + { + /* + * For some reason, SendInput may hit deadlock if it is called in the InputHookProc thread. + * For now leave it as is in the caller thread which is the socket receiver thread. + * */ + + // SendInput is thread-safe but few users seem to hit a deadlock occasionally, probably a Windows bug. + lock (InputSimulationLock) + { + target(); + } + } + + internal static void SendPackage(ID des, PackageType packageType) + { + DATA package = new(); + package.Type = packageType; + package.Des = des; + package.MachineName = MachineName; + + SkSend(package, null, false); + } + + internal static void SendHeartBeat(bool initial = false) + { + SendPackage(ID.ALL, initial && Encryption.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat); + } + + private static long lastSendNextMachine; + + internal static void SendNextMachine(ID hostMachine, ID nextMachine, Point requestedXY) + { + Logger.LogDebug($"SendNextMachine: Host machine: {hostMachine}, Next machine: {nextMachine}, Requested XY: {requestedXY}"); + + if (GetTick() - lastSendNextMachine < 100) + { + Logger.LogDebug("Machine switching in progress."); // "Move Mouse relatively" mode, slow machine/network, quick/busy hand. + return; + } + + lastSendNextMachine = GetTick(); + + DATA package = new(); + package.Type = PackageType.NextMachine; + + package.Des = hostMachine; + + package.Md.X = requestedXY.X; + package.Md.Y = requestedXY.Y; + package.Md.WheelDelta = (int)nextMachine; + + SkSend(package, null, false); + + Logger.LogDebug("SendNextMachine done."); + } + + private static ulong lastInputEventCount; + private static ulong lastRealInputEventCount; + + internal static void SendAwakeBeat() + { + if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() && + Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount) + { + SendPackage(ID.ALL, PackageType.Awake); + } + else + { + SendHeartBeat(); + } + + lastInputEventCount = Event.InputEventCount; + lastRealInputEventCount = Event.RealInputEventCount; + } + + internal static void HumanBeingDetected() + { + if (lastInputEventCount == Event.InputEventCount) + { + if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive()) + { + PokeMyself(); + } + } + + lastInputEventCount = Event.InputEventCount; + } + + private static void PokeMyself() + { + int x, y = 0; + + for (int i = 0; i < 10; i++) + { + x = Encryption.Ran.Next(-9, 10); + InputSimulation.MoveMouseRelative(x, y); + Thread.Sleep(50); + InputSimulation.MoveMouseRelative(-x, -y); + Thread.Sleep(50); + + if (lastInputEventCount != Event.InputEventCount) + { + break; + } + } + } + + internal static void InitLastInputEventCount() + { + lastInputEventCount = Event.InputEventCount; + lastRealInputEventCount = Event.RealInputEventCount; + } + + internal static void SendHello() + { + SendPackage(ID.ALL, PackageType.Hello); + } + + /* + internal static void SendHi() + { + SendPackage(IP.ALL, PackageType.hi); + } + * */ + + internal static void SendByeBye() + { + Logger.LogDebug($"{nameof(SendByeBye)}"); + SendPackage(ID.ALL, PackageType.ByeBye); + } + + internal static void SendClipboardBeat() + { + SendPackage(ID.ALL, PackageType.Clipboard); + } + + internal static void ProcessByeByeMessage(DATA package) + { + if (package.Src == MachineStuff.desMachineID) + { + MachineStuff.SwitchToMachine(MachineName.Trim()); + } + + _ = MachineStuff.RemoveDeadMachines(package.Src); + } + + internal static long GetTick() // ms + { + return DateTime.Now.Ticks / 10000; + } + + internal static void SetToggleIcon(int[] toggleIcons) + { + Logger.LogDebug($"{nameof(SetToggleIcon)}: {toggleIcons?.FirstOrDefault()}"); + Common.toggleIcons = toggleIcons; + toggleIconsIndex = 0; + } + + internal static string CaptureScreen() + { + try + { + string fileName = GetMyStorageDir() + @"ScreenCaptureByMouseWithoutBorders.png"; + int w = MachineStuff.desktopBounds.Right - MachineStuff.desktopBounds.Left; + int h = MachineStuff.desktopBounds.Bottom - MachineStuff.desktopBounds.Top; + Bitmap bm = new(w, h); + Graphics g = Graphics.FromImage(bm); + Size s = new(w, h); + g.CopyFromScreen(MachineStuff.desktopBounds.Left, MachineStuff.desktopBounds.Top, 0, 0, s); + bm.Save(fileName, ImageFormat.Png); + bm.Dispose(); + return fileName; + } + catch (Exception e) + { + Logger.Log(e); + return null; + } + } + + private static void PrepareScreenCapture() + { + Common.DoSomethingInUIThread(() => + { + if (!DragDrop.MouseDown && Helper.SendMessageToHelper(0x401, IntPtr.Zero, IntPtr.Zero) > 0) + { + Common.MMSleep(0.2); + InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT }); + InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)WM.LLKHF.UP, wVk = (int)VK.SNAPSHOT }); + + Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated."); + + _ = NativeMethods.MoveWindow( + (IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT), + MachineStuff.DesktopBounds.Left, + MachineStuff.DesktopBounds.Top, + MachineStuff.DesktopBounds.Right - MachineStuff.DesktopBounds.Left, + MachineStuff.DesktopBounds.Bottom - MachineStuff.DesktopBounds.Top, + false); + + _ = Helper.SendMessageToHelper(0x406, IntPtr.Zero, IntPtr.Zero, false); + } + else + { + Logger.Log("PrepareScreenCapture: Validation failed."); + } + }); + } + + internal static void OpenImage(string file) + { + // We want to run mspaint under the user account who ran explorer.exe (who logged in this current input desktop) + + // ImpersonateLoggedOnUserAndDoSomething(delegate() + // { + // Process.Start("explorer", "\"" + file + "\""); + // }); + _ = Launch.CreateProcessInInputDesktopSession( + "\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") + + "\"", + "\"" + file + "\"", + WinAPI.GetInputDesktop(), + 1); + + // CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") + + // " \"" + file + "\""); + + // We don't want to run mspaint as local system account + /* + ProcessStartInfo s = new ProcessStartInfo( + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe"), + "\"" + file + "\""); + s.WindowStyle = ProcessWindowStyle.Maximized; + Process.Start(s); + * */ + } + + internal static void SendImage(string machine, string file) + { + Clipboard.LastDragDropFile = file; + + // Send ClipboardCapture + if (machine.Equals("All", StringComparison.OrdinalIgnoreCase)) + { + SendPackage(ID.ALL, PackageType.ClipboardCapture); + } + else + { + ID id = MachineStuff.MachinePool.ResolveID(machine); + if (id != ID.NONE) + { + SendPackage(id, PackageType.ClipboardCapture); + } + } + } + + internal static void SendImage(ID src, string file) + { + Clipboard.LastDragDropFile = file; + + // Send ClipboardCapture + SendPackage(src, PackageType.ClipboardCapture); + } + + internal static void ShowToolTip(string tip, int timeOutInMilliseconds = 5000, ToolTipIcon icon = ToolTipIcon.Info, bool showBalloonTip = true, bool forceEvenIfHidingOldUI = false) + { + if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) + { + DoSomethingInUIThread(() => + { + if (Setting.Values.FirstRun) + { + MachineStuff.Settings?.ShowTip(icon, tip, timeOutInMilliseconds); + } + + Common.MatrixForm?.ShowTip(icon, tip, timeOutInMilliseconds); + + if (showBalloonTip) + { + if (MainForm != null) + { + MainForm.ShowToolTip(tip, timeOutInMilliseconds, forceEvenIfHidingOldUI: forceEvenIfHidingOldUI); + } + else + { + Logger.Log(tip); + } + } + }); + } + } + + private static FrmMessage topMostMessageForm; + + internal static void ToggleShowTopMostMessage(string text, string bigText, int timeOut) + { + DoSomethingInUIThread(() => + { + if (topMostMessageForm == null) + { + topMostMessageForm = new FrmMessage(text, bigText, timeOut); + topMostMessageForm.Show(); + } + else + { + FrmMessage currentMessageForm = topMostMessageForm; + topMostMessageForm = null; + currentMessageForm.Close(); + } + }); + } + + internal static void HideTopMostMessage() + { + DoSomethingInUIThread(() => + { + topMostMessageForm?.Close(); + }); + } + + internal static void NullTopMostMessage() + { + DoSomethingInUIThread(() => + { + if (topMostMessageForm != null) + { + topMostMessageForm = null; + } + }); + } + + internal static bool IsTopMostMessageNotNull() + { + return topMostMessageForm != null; + } + + private static bool TestSend(TcpSk t) + { + ID remoteMachineID; + + if (t.Status == SocketStatus.Connected) + { + try + { + DATA package = new(); + package.Type = PackageType.Hi; + package.Des = remoteMachineID = (ID)t.MachineId; + package.MachineName = MachineName; + + _ = Sk.TcpSend(t, package); + t.EncryptedStream?.Flush(); + + return true; + } + catch (ExpectedSocketException) + { + t.BackingSocket = null; // To be removed at CloseAnUnusedSocket() + } + } + + t.Status = SocketStatus.SendError; + return false; + } + + internal static bool IsConnectedTo(ID remoteMachineID) + { + bool updateClientSockets = false; + + if (remoteMachineID == MachineID) + { + return true; + } + + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + if (sk.TcpSockets != null) + { + foreach (TcpSk t in sk.TcpSockets) + { + if (t.Status == SocketStatus.Connected && (uint)remoteMachineID == t.MachineId) + { + if (TestSend(t)) + { + return true; + } + else + { + updateClientSockets = true; + } + } + } + } + } + } + + if (updateClientSockets) + { + MachineStuff.UpdateClientSockets(nameof(IsConnectedTo)); + } + + return false; + } + +#if DEBUG + private static long minSendTime = long.MaxValue; + private static long avgSendTime; + private static long maxSendTime; + private static long totalSendCount; + private static long totalSendTime; +#endif + + internal static void SkSend(DATA data, uint? exceptDes, bool includeHandShakingSockets) + { + bool connected = false; + + SocketStuff sk = Sk; + + if (sk != null) + { +#if DEBUG + long startStop = DateTime.Now.Ticks; + totalSendCount++; +#endif + + try + { + data.Id = Interlocked.Increment(ref Package.PackageID); + + bool updateClientSockets = false; + + lock (sk.TcpSocketsLock) + { + foreach (TcpSk t in sk.TcpSockets) + { + if (t != null && t.BackingSocket != null && (t.Status == SocketStatus.Connected || (t.Status == SocketStatus.Handshaking && includeHandShakingSockets))) + { + if (t.MachineId == (uint)data.Des || (data.Des == ID.ALL && t.MachineId != exceptDes && MachineStuff.InMachineMatrix(t.MachineName))) + { + try + { + sk.TcpSend(t, data); + + if (data.Des != ID.ALL) + { + connected = true; + } + } + catch (ExpectedSocketException) + { + t.BackingSocket = null; // To be removed at CloseAnUnusedSocket() + updateClientSockets = true; + } + catch (Exception e) + { + Logger.Log(e); + t.BackingSocket = null; // To be removed at CloseAnUnusedSocket() + updateClientSockets = true; + } + } + } + } + } + + if (!connected && data.Des != ID.ALL) + { + Logger.LogDebug("********** No active connection found for the remote machine! **********" + data.Des.ToString()); + + if (data.Des == ID.NONE || MachineStuff.RemoveDeadMachines(data.Des)) + { + // SwitchToMachine(MachineName.Trim()); + MachineStuff.NewDesMachineID = DesMachineID = MachineID; + MachineStuff.SwitchLocation.X = Event.XY_BY_PIXEL + Event.myLastX; + MachineStuff.SwitchLocation.Y = Event.XY_BY_PIXEL + Event.myLastY; + MachineStuff.SwitchLocation.ResetCount(); + EvSwitch.Set(); + } + } + + if (updateClientSockets) + { + MachineStuff.UpdateClientSockets("SkSend"); + } + } + catch (Exception e) + { + Logger.Log(e); + } + +#if DEBUG + startStop = DateTime.Now.Ticks - startStop; + totalSendTime += startStop; + if (startStop < minSendTime) + { + minSendTime = startStop; + } + + if (startStop > maxSendTime) + { + maxSendTime = startStop; + } + + avgSendTime = totalSendTime / totalSendCount; +#endif + } + else + { + Package.PackageSent.Nil++; + } + } + + internal static void CloseAnUnusedSocket() + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + if (sk.TcpSockets != null) + { + TcpSk tobeRemoved = null; + + foreach (TcpSk t in sk.TcpSockets) + { + if ((t.Status != SocketStatus.Connected && t.BirthTime < GetTick() - SocketStuff.CONNECT_TIMEOUT) || t.BackingSocket == null) + { + Logger.LogDebug("CloseAnUnusedSocket: " + t.MachineName + ":" + t.MachineId + "|" + t.Status.ToString()); + tobeRemoved = t; + + if (t.BackingSocket != null) + { + try + { + t.BackingSocket.Close(); + } + catch (Exception e) + { + Logger.Log(e); + } + } + + break; // Each time we try to remove one socket only. + } + } + + if (tobeRemoved != null) + { + _ = sk.TcpSockets.Remove(tobeRemoved); + } + } + } + } + } + + internal static bool AtLeastOneSocketConnected() + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + if (sk.TcpSockets != null) + { + foreach (TcpSk t in sk.TcpSockets) + { + if (t.Status == SocketStatus.Connected) + { + Logger.LogDebug("AtLeastOneSocketConnected returning true: " + t.MachineName); + return true; + } + } + } + } + } + + Logger.LogDebug("AtLeastOneSocketConnected returning false."); + return false; + } + + private static Socket AtLeastOneServerSocketConnected() + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + if (sk.TcpSockets != null) + { + foreach (TcpSk t in sk.TcpSockets) + { + if (!t.IsClient && t.Status == SocketStatus.Connected) + { + Logger.LogDebug("AtLeastOneServerSocketConnected returning true: " + t.MachineName); + return t.BackingSocket; + } + } + } + } + } + + Logger.LogDebug("AtLeastOneServerSocketConnected returning false."); + return null; + } + + internal static TcpSk GetConnectedClientSocket() + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + return sk.TcpSockets?.FirstOrDefault(item => item.IsClient && item.Status == SocketStatus.Connected); + } + } + else + { + return null; + } + } + + internal static bool AtLeastOneSocketEstablished() + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + if (sk.TcpSockets != null) + { + foreach (TcpSk t in sk.TcpSockets) + { + if (t.BackingSocket != null && t.BackingSocket.Connected) + { + if (TestSend(t)) + { + Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning true: {t.MachineName}"); + return true; + } + } + } + } + } + } + + Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning false."); + return false; + } + + internal static bool IsConnectedByAClientSocketTo(string machineName) + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + foreach (TcpSk t in sk.TcpSockets) + { + if (t != null && t.IsClient && t.Status == SocketStatus.Connected + && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + } + + return false; + } + + internal static IPAddress GetConnectedClientSocketIPAddressFor(string machineName) + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + return sk.TcpSockets.FirstOrDefault(t => t != null && t.IsClient && t.Status == SocketStatus.Connected + && t.Address != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase)) + ?.Address; + } + } + + return null; + } + + internal static bool IsConnectingByAClientSocketTo(string machineName, IPAddress ip) + { + SocketStuff sk = Common.Sk; + + if (sk != null) + { + lock (sk.TcpSocketsLock) + { + foreach (TcpSk t in sk.TcpSockets) + { + if (t != null && t.IsClient && t.Status == SocketStatus.Connecting + && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase) + && t.Address.ToString().Equals(ip.ToString(), StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + } + + return false; + } + + internal static void UpdateSetupMachineMatrix(string desMachine) + { + int machineCt = 0; + + foreach (string m in MachineStuff.MachineMatrix) + { + if (!string.IsNullOrEmpty(m.Trim())) + { + machineCt++; + } + } + + if (machineCt < 2 && MachineStuff.Settings != null && (MachineStuff.Settings.GetCurrentPage() is SetupPage1 || MachineStuff.Settings.GetCurrentPage() is SetupPage2b)) + { + MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { Common.MachineName.Trim(), desMachine, string.Empty, string.Empty }; + Logger.LogDebug("UpdateSetupMachineMatrix: " + string.Join(",", MachineStuff.MachineMatrix)); + + Common.DoSomethingInUIThread( + () => + { + MachineStuff.Settings.SetControlPage(new SetupPage4()); + }, + true); + } + } + + internal static void ReopenSockets(bool byUser) + { + DoSomethingInUIThread( + () => + { + try + { + SocketStuff tmpSk = Sk; + + if (tmpSk != null) + { + Sk = null; // TODO: This looks redundant. + tmpSk.Close(byUser); + } + + Sk = new SocketStuff(tcpPort, byUser); + } + catch (Exception e) + { + Sk = null; + Logger.Log(e); + } + + if (Sk != null) + { + if (byUser) + { + SocketStuff.ClearBadIPs(); + } + + MachineStuff.UpdateClientSockets("ReopenSockets"); + } + }, + true); + + if (Sk == null) + { + return; + } + + Common.DoSomethingInTheInputCallbackThread(() => + { + if (Common.Hook != null) + { + Common.Hook.Stop(); + Common.Hook = null; + } + + if (byUser) + { + Common.InputCallbackForm.Close(); + Common.InputCallbackForm = null; + Program.StartInputCallbackThread(); + } + else + { + Common.InputCallbackForm.InstallKeyboardAndMouseHook(); + } + }); + } + + internal static string GetMyStorageDir() + { + string st = string.Empty; + + try + { + if (RunOnLogonDesktop || RunOnScrSaverDesktop) + { + st = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + if (!Directory.Exists(st)) + { + _ = Directory.CreateDirectory(st); + } + + st += @"\" + Common.BinaryName; + if (!Directory.Exists(st)) + { + _ = Directory.CreateDirectory(st); + } + + st += @"\ScreenCaptures\"; + if (!Directory.Exists(st)) + { + _ = Directory.CreateDirectory(st); + } + } + else + { + _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => + { + st = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\" + Common.BinaryName; + if (!Directory.Exists(st)) + { + _ = Directory.CreateDirectory(st); + } + + st += @"\ScreenCaptures\"; + if (!Directory.Exists(st)) + { + _ = Directory.CreateDirectory(st); + } + }); + } + + Logger.LogDebug("GetMyStorageDir: " + st); + + // Delete old files. + foreach (FileInfo fi in new DirectoryInfo(st).GetFiles()) + { + if (fi.CreationTime.AddDays(1) < DateTime.Now) + { + fi.Delete(); + } + } + + return st; + } + catch (Exception e) + { + Logger.Log(e); + + if (string.IsNullOrEmpty(st) || !st.Contains(Common.BinaryName)) + { + st = Path.GetTempPath(); + } + + return st; + } + } + + internal static void GetMachineName() + { + string machine_Name = string.Empty; + + try + { + machine_Name = Dns.GetHostName(); + Logger.LogDebug("GetHostName = " + machine_Name); + } + catch (Exception e) + { + Logger.Log(e); + + if (string.IsNullOrEmpty(machine_Name)) + { + machine_Name = "RANDOM" + Encryption.Ran.Next().ToString(CultureInfo.CurrentCulture); + } + } + + if (machine_Name.Length > 32) + { + machine_Name = machine_Name[..32]; + } + + Common.MachineName = machine_Name.Trim(); + + Logger.LogDebug($"========== {nameof(GetMachineName)} ended!"); + } + + private static string GetNetworkName(NetworkInterface networkInterface) + { + return $"{networkInterface.Name} | {networkInterface.Description.Replace(":", "-")}"; + } + + internal static string GetRemoteStringIP(Socket s, bool throwException = false) + { + if (s == null) + { + return string.Empty; + } + + string ip; + + try + { + ip = (s?.RemoteEndPoint as IPEndPoint)?.Address?.ToString(); + + if (string.IsNullOrEmpty(ip)) + { + return string.Empty; + } + } + catch (ObjectDisposedException e) + { + Logger.Log($"{nameof(GetRemoteStringIP)}: The socket could have been disposed by other threads, error: {e.Message}"); + + if (throwException) + { + throw; + } + + return string.Empty; + } + catch (SocketException e) + { + Logger.Log($"{nameof(GetRemoteStringIP)}: {e.Message}"); + + if (throwException) + { + throw; + } + + return string.Empty; + } + + return ip; + } + + internal static void CloseAllFormsAndHooks() + { + if (Hook != null) + { + Hook.Stop(); + Hook = null; + if (InputCallbackForm != null) + { + DoSomethingInTheInputCallbackThread(() => + { + InputCallbackForm.Close(); + InputCallbackForm = null; + }); + } + } + + if (MainForm != null) + { + MainForm.Destroy(); + MainForm = null; + } + + if (MatrixForm != null) + { + MatrixForm.Close(); + MatrixForm = null; + } + + if (AboutForm != null) + { + AboutForm.Close(); + AboutForm = null; + } + } + + internal static void MoveMouseToCenter() + { + Logger.LogDebug("+++++ MoveMouseToCenter"); + InputSimulation.MoveMouse( + MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2), + MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2)); + } + + internal static void HideMouseCursor(bool byHideMouseMessage) + { + Common.LastPos = new Point( + MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2), + Setting.Values.HideMouse ? 4 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2)); + + if ((MachineStuff.desMachineID != MachineID && MachineStuff.desMachineID != ID.ALL) || byHideMouseMessage) + { + _ = NativeMethods.SetCursorPos(Common.LastPos.X, Common.LastPos.Y); + _ = NativeMethods.GetCursorPos(ref Common.lastPos); + Logger.LogDebug($"+++++ HideMouseCursor, byHideMouseMessage = {byHideMouseMessage}"); + } + + CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue); + } + + internal static string GetText(IntPtr hWnd) + { + int length = NativeMethods.GetWindowTextLength(hWnd); + StringBuilder sb = new(length + 1); + int rv = NativeMethods.GetWindowText(hWnd, sb, sb.Capacity); + Logger.LogDebug("GetWindowText returned " + rv.ToString(CultureInfo.CurrentCulture)); + return sb.ToString(); + } + + private static string GetWindowClassName(IntPtr hWnd) + { + StringBuilder buffer = new(128); + _ = NativeMethods.GetClassName(hWnd, buffer, buffer.Capacity); + return buffer.ToString(); + } + + internal static void MMSleep(double secs) + { + for (int i = 0; i < secs * 10; i++) + { + Application.DoEvents(); + Thread.Sleep(100); + } + } + + internal static void UpdateMultipleModeIconAndMenu() + { + MainForm?.UpdateMultipleModeIconAndMenu(); + } + + internal static void SendOrReceiveARandomDataBlockPerInitialIV(Stream st, bool send = true) + { + byte[] ranData = new byte[Encryption.SymAlBlockSize]; + + try + { + if (send) + { + ranData = RandomNumberGenerator.GetBytes(Encryption.SymAlBlockSize); + st.Write(ranData, 0, ranData.Length); + } + else + { + int toRead = ranData.Length; + int read = st.ReadEx(ranData, 0, toRead); + + if (read != toRead) + { + Logger.LogDebug("Stream has no more data after reading {0} bytes.", read); + } + } + } + catch (IOException e) + { + string log = $"{nameof(SendOrReceiveARandomDataBlockPerInitialIV)}: Exception {(send ? "writing" : "reading")} to the socket stream: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)"; + Logger.Log(log); + + if (e.InnerException is not (SocketException or ObjectDisposedException)) + { + throw; + } + } + } + + private static bool DisableEasyMouseWhenForegroundWindowIsFullscreenSetting() + { + return Setting.Values.DisableEasyMouseWhenForegroundWindowIsFullscreen; + } + + private static bool IsAppIgnoredByEasyMouseFullscreenCheck(IntPtr foregroundWindowHandle) + { + if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out var processId) == 0) + { + Logger.LogDebug($"GetWindowThreadProcessId failed with error : {Marshal.GetLastWin32Error()}"); + return false; + } + + var processHandle = NativeMethods.OpenProcess(0x1000, false, processId); + if (processHandle == IntPtr.Zero) + { + return false; + } + + uint maxPath = 260; + var nameBuffer = new char[maxPath]; + if (!NativeMethods.QueryFullProcessImageName( + processHandle, NativeMethods.QUERY_FULL_PROCESS_NAME_FLAGS.DEFAULT, nameBuffer, ref maxPath)) + { + Logger.LogDebug($"QueryFullProcessImageName failed with error : {Marshal.GetLastWin32Error()}"); + NativeMethods.CloseHandle(processHandle); + return false; + } + + NativeMethods.CloseHandle(processHandle); + + var name = new string(nameBuffer, 0, (int)maxPath); + + var excludedApps = Setting.Values.EasyMouseFullscreenSwitchBlockExcludedApps; + + return excludedApps.Contains(Path.GetFileNameWithoutExtension(name), StringComparer.OrdinalIgnoreCase) + || excludedApps.Contains(Path.GetFileName(name), StringComparer.OrdinalIgnoreCase); + } + + private static bool IsEasyMouseBlockedByFullscreenWindow() + { + var shellHandle = NativeMethods.GetShellWindow(); + var desktopHandle = NativeMethods.GetDesktopWindow(); + var foregroundHandle = NativeMethods.GetForegroundWindow(); + + // If the foreground window is either the desktop or the Windows shell, we are not in fullscreen mode. + if (foregroundHandle.Equals(shellHandle) || foregroundHandle.Equals(desktopHandle)) + { + return false; + } + + if (NativeMethods.SHQueryUserNotificationState(out var userNotificationState) != 0) + { + Logger.LogDebug($"SHQueryUserNotificationState failed with error : {Marshal.GetLastWin32Error()}"); + return false; + } + + switch (userNotificationState) + { + // An application running in full screen mode, check if the foreground window is + // listed as ignored in the settings. + case NativeMethods.USER_NOTIFICATION_STATE.BUSY: + case NativeMethods.USER_NOTIFICATION_STATE.RUNNING_D3D_FULL_SCREEN: + case NativeMethods.USER_NOTIFICATION_STATE.PRESENTATION_MODE: + return !IsAppIgnoredByEasyMouseFullscreenCheck(foregroundHandle); + + // No full screen app running. + case NativeMethods.USER_NOTIFICATION_STATE.NOT_PRESENT: + case NativeMethods.USER_NOTIFICATION_STATE.ACCEPTS_NOTIFICATIONS: + case NativeMethods.USER_NOTIFICATION_STATE.QUIET_TIME: + // Cannot determine + case NativeMethods.USER_NOTIFICATION_STATE.APP: + default: + return false; + } + } + + /// + /// Check if a machine switch triggered by EasyMouse would be allowed to proceed due to other settings. + /// + /// A boolean that tells us if the switch isn't blocked by any other settings + internal static bool IsEasyMouseSwitchAllowed() + { + // Never prevent a switch if we are not moving out of the host machine. + if (!DisableEasyMouseWhenForegroundWindowIsFullscreenSetting() || DesMachineID != MachineID) + { + return true; + } + + // Check if the switch is blocked by a full-screen window running in the foreground + return !IsEasyMouseBlockedByFullscreenWindow(); + } +} diff --git a/src/modules/MouseWithoutBorders/App/Core/Helper.cs b/src/modules/MouseWithoutBorders/App/Core/Helper.cs index 8c291fb417..2d97d91123 100644 --- a/src/modules/MouseWithoutBorders/App/Core/Helper.cs +++ b/src/modules/MouseWithoutBorders/App/Core/Helper.cs @@ -295,9 +295,9 @@ internal static class Helper return; } - if (!Common.IpcChannelCreated) + if (!IpcChannelHelper.IpcChannelCreated) { - Logger.TelemetryLogTrace($"{nameof(Common.IpcChannelCreated)} = {Common.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning); + Logger.TelemetryLogTrace($"{nameof(IpcChannelHelper.IpcChannelCreated)} = {IpcChannelHelper.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning); return; } diff --git a/src/modules/MouseWithoutBorders/App/Core/IpcChannelHelper.cs b/src/modules/MouseWithoutBorders/App/Core/IpcChannelHelper.cs new file mode 100644 index 0000000000..7e6bfd0217 --- /dev/null +++ b/src/modules/MouseWithoutBorders/App/Core/IpcChannelHelper.cs @@ -0,0 +1,53 @@ +// 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.Threading; +using System.Windows.Forms; + +#if !MM_HELPER +using Thread = MouseWithoutBorders.Core.Thread; +#endif + +namespace MouseWithoutBorders.Core; + +internal static class IpcChannelHelper +{ + internal static bool IpcChannelCreated { get; set; } + + internal static T Retry(string name, Func func, Action log, Action preRetry = null) + { + int count = 0; + + do + { + try + { + T rv = func(); + + if (count > 0) + { + log($"Trace: {name} has been successful after {count} retry."); + } + + return rv; + } + catch (Exception) + { + count++; + + preRetry?.Invoke(); + + if (count > 10) + { + throw; + } + + Application.DoEvents(); + Thread.Sleep(200); + } + } + while (true); + } +} diff --git a/src/modules/MouseWithoutBorders/App/Core/Logger.cs b/src/modules/MouseWithoutBorders/App/Core/Logger.cs index 4d39983c35..334d269400 100644 --- a/src/modules/MouseWithoutBorders/App/Core/Logger.cs +++ b/src/modules/MouseWithoutBorders/App/Core/Logger.cs @@ -198,7 +198,6 @@ internal static class Logger } Logger.DumpProgramLogs(sb, level); - Logger.DumpOtherLogs(sb, level); Logger.DumpStaticTypes(sb, level); log = string.Format( @@ -240,19 +239,16 @@ internal static class Logger _ = Logger.PrivateDump(sb, AllLogs, "[Program logs]\r\n===============\r\n", 0, level, false); } - internal static void DumpOtherLogs(StringBuilder sb, int level) - { - _ = Logger.PrivateDump(sb, new Common(), "[Other Logs]\r\n===============\r\n", 0, level, false); - } - internal static void DumpStaticTypes(StringBuilder sb, int level) { var staticTypes = new List { typeof(Clipboard), + typeof(Common), typeof(DragDrop), typeof(Encryption), typeof(Event), + typeof(IpcChannelHelper), typeof(InitAndCleanup), typeof(Helper), typeof(Launch), diff --git a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs index 5649ae8d7f..bb802aa159 100644 --- a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs +++ b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs @@ -2,6 +2,8 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MouseWithoutBorders.Core; + namespace MouseWithoutBorders { public partial class SetupPage2b : SettingsFormPage diff --git a/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs b/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs index 3a7ae9901c..069c428589 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs @@ -15,6 +15,8 @@ using System.Globalization; using System.Reflection; using System.Windows.Forms; +using MouseWithoutBorders.Core; + namespace MouseWithoutBorders { internal partial class FrmAbout : System.Windows.Forms.Form, IDisposable diff --git a/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs b/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs index 2abb18f932..e258df9709 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs @@ -6,6 +6,8 @@ using System; using System.Globalization; using System.Windows.Forms; +using MouseWithoutBorders.Core; + namespace MouseWithoutBorders { public partial class FrmMessage : System.Windows.Forms.Form diff --git a/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs b/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs index b4c2f13efd..e5b8095d18 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs @@ -6,6 +6,7 @@ using System; using System.Windows.Forms; using MouseWithoutBorders.Class; +using MouseWithoutBorders.Core; namespace MouseWithoutBorders { diff --git a/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj b/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj index f97c0c44f3..981266c0cb 100644 --- a/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj +++ b/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj @@ -49,6 +49,9 @@ IClipboardHelper.cs + + IpcChannelHelper.cs + diff --git a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt index 34a83830cd..3a36e9b3d2 100644 --- a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt +++ b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt @@ -1,9 +1,38 @@ [Program logs] =============== = System.String[] -[Other Logs] +[Clipboard] +=============== +Comma = System.Char[] +--System.Char[] = System.Char[]: N/A +Star = System.Char[] +--System.Char[] = System.Char[]: N/A +NullSeparator = System.Char[] +--System.Char[] = System.Char[]: N/A +lastClipboardEventTime = 0 +clipboardCopiedTime = 0 +k__BackingField = NONE +k__BackingField = 0 +k__BackingField = False +lastClipboardObject = +k__BackingField = False +ClipboardThreadOldLock = Lock +--_owningThreadId = 0 +--_state = 0 +--_recursionCount = 0 +--_spinCount = 22 +--_waiterStartTimeMs = 0 +--s_contentionCount = 0 +--s_maxSpinCount = 22 +--s_minSpinCountForAdaptiveSpin = -100 +BIG_CLIPBOARD_DATA_TIMEOUT = 30000 +MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576 +MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600 +TEXT_HEADER_SIZE = 12 +DATA_SIZE = 48 +TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F} +[Common] =============== - = MouseWithoutBorders.Common screenWidth = 0 screenHeight = 0 lastX = 0 @@ -46,7 +75,6 @@ avgSendTime = 0 maxSendTime = 0 totalSendCount = 0 totalSendTime = 0 -k__BackingField = False TOGGLE_ICONS_SIZE = 4 ICON_ONE = 0 ICON_ALL = 1 @@ -55,36 +83,6 @@ ICON_BIG_CLIPBOARD = 3 ICON_ERROR = 4 JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999 NETWORK_STREAM_BUF_SIZE = 1048576 -[Clipboard] -=============== -Comma = System.Char[] ---System.Char[] = System.Char[]: N/A -Star = System.Char[] ---System.Char[] = System.Char[]: N/A -NullSeparator = System.Char[] ---System.Char[] = System.Char[]: N/A -lastClipboardEventTime = 0 -clipboardCopiedTime = 0 -k__BackingField = NONE -k__BackingField = 0 -k__BackingField = False -lastClipboardObject = -k__BackingField = False -ClipboardThreadOldLock = Lock ---_owningThreadId = 0 ---_state = 0 ---_recursionCount = 0 ---_spinCount = 22 ---_waiterStartTimeMs = 0 ---s_contentionCount = 0 ---s_maxSpinCount = 22 ---s_minSpinCountForAdaptiveSpin = -100 -BIG_CLIPBOARD_DATA_TIMEOUT = 30000 -MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576 -MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600 -TEXT_HEADER_SIZE = 12 -DATA_SIZE = 48 -TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F} [DragDrop] =============== isDragging = False @@ -174,6 +172,9 @@ actualLastPos = {X=0,Y=0} --Empty = {X=0,Y=0} myLastX = 0 myLastY = 0 +[IpcChannelHelper] +=============== +k__BackingField = False [InitAndCleanup] =============== initDone = False @@ -440,6 +441,7 @@ WM_LBUTTONDBLCLK = 515 WM_RBUTTONDBLCLK = 518 WM_MBUTTONDBLCLK = 521 WM_MOUSEWHEEL = 522 +WM_MOUSEHWHEEL = 526 WM_KEYDOWN = 256 WM_KEYUP = 257 WM_SYSKEYDOWN = 260 diff --git a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs index 6ca983724c..f7cd3b461a 100644 --- a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs +++ b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs @@ -117,7 +117,6 @@ public static class LoggerTests // copied from DumpObjects in Logger.cs var sb = new StringBuilder(1000000); Logger.DumpProgramLogs(sb, settingsDumpObjectsLevel); - Logger.DumpOtherLogs(sb, settingsDumpObjectsLevel); Logger.DumpStaticTypes(sb, settingsDumpObjectsLevel); var actual = sb.ToString();