From 6ec8ab700b7973e691a1e0e3c30a407363a45815 Mon Sep 17 00:00:00 2001 From: Michael Clayton Date: Wed, 24 Sep 2025 07:55:57 +0100 Subject: [PATCH] [Mouse Without Borders] - refactoring "Common" classes (Part 5 of 7) (#38500) ## Summary of the Pull Request **Part 5** 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.Clipboard.cs``` -> ```Clipboard.cs``` * ```Common.InitAndCleanup.cs``` -> ```InitAndCleanup.cs``` * Update references to the types in the new locations * Update unit test to verify functionality has only changed in an expected way ## PR Checklist - [x] Partially 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" 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``` --- .../App/Class/Common.Clipboard.cs | 1162 ----------------- .../App/Class/Common.InitAndCleanup.cs | 284 ---- .../App/Class/Common.WinAPI.cs | 4 +- .../MouseWithoutBorders/App/Class/Common.cs | 17 +- .../App/Class/IClipboardHelper.cs | 3 +- .../App/Class/InputHook.cs | 4 +- .../App/Class/InputSimulation.cs | 6 +- .../MouseWithoutBorders/App/Class/Program.cs | 8 +- .../MouseWithoutBorders/App/Class/Setting.cs | 2 +- .../App/Class/SocketStuff.cs | 47 +- .../MouseWithoutBorders/App/Core/Clipboard.cs | 1155 ++++++++++++++++ .../MouseWithoutBorders/App/Core/DragDrop.cs | 16 +- .../MouseWithoutBorders/App/Core/Event.cs | 8 +- .../MouseWithoutBorders/App/Core/Helper.cs | 4 +- .../App/Core/InitAndCleanup.cs | 278 ++++ .../MouseWithoutBorders/App/Core/Logger.cs | 34 +- .../App/Core/MachineStuff.cs | 4 +- .../MouseWithoutBorders/App/Core/Receiver.cs | 30 +- .../App/Form/Settings/SetupPage3a.cs | 2 +- .../MouseWithoutBorders/App/Form/frmMatrix.cs | 6 +- .../MouseWithoutBorders/App/Form/frmScreen.cs | 26 +- .../Core/Logger.PrivateDump.expected.txt | 82 +- 22 files changed, 1591 insertions(+), 1591 deletions(-) delete mode 100644 src/modules/MouseWithoutBorders/App/Class/Common.Clipboard.cs delete mode 100644 src/modules/MouseWithoutBorders/App/Class/Common.InitAndCleanup.cs create mode 100644 src/modules/MouseWithoutBorders/App/Core/Clipboard.cs create mode 100644 src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.Clipboard.cs b/src/modules/MouseWithoutBorders/App/Class/Common.Clipboard.cs deleted file mode 100644 index 51be48ec5a..0000000000 --- a/src/modules/MouseWithoutBorders/App/Class/Common.Clipboard.cs +++ /dev/null @@ -1,1162 +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.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; - -using Microsoft.PowerToys.Telemetry; - -// -// Clipboard related routines. -// -// -// 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 SystemClipboard = System.Windows.Forms.Clipboard; -using Thread = MouseWithoutBorders.Core.Thread; - -namespace MouseWithoutBorders -{ - internal partial class Common - { - internal static readonly char[] Comma = new char[] { ',' }; - internal static readonly char[] Star = new char[] { '*' }; - internal static readonly char[] NullSeparator = new char[] { '\0' }; - - internal const uint BIG_CLIPBOARD_DATA_TIMEOUT = 30000; - private const uint MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1024 * 1024; // 1MB - private const uint MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 100 * 1024 * 1024; // 100MB - private const int TEXT_HEADER_SIZE = 12; - private const int DATA_SIZE = 48; - private const string TEXT_TYPE_SEP = "{4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}"; - private static long lastClipboardEventTime; - private static string lastMachineWithClipboardData; - private static string lastDragDropFile; -#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter - internal static long clipboardCopiedTime; -#pragma warning restore SA1307 - - internal static ID LastIDWithClipboardData { get; set; } - - internal static string LastDragDropFile - { - get => Common.lastDragDropFile; - set => Common.lastDragDropFile = value; - } - - internal static string LastMachineWithClipboardData - { - get => Common.lastMachineWithClipboardData; - set => Common.lastMachineWithClipboardData = value; - } - - internal static long LastClipboardEventTime - { - get => Common.lastClipboardEventTime; - set => Common.lastClipboardEventTime = value; - } - - internal static IntPtr NextClipboardViewer { get; set; } - - internal static bool IsClipboardDataImage { get; private set; } - - internal static byte[] LastClipboardData { get; private set; } - - private static object lastClipboardObject = string.Empty; - - internal static bool HasSwitchedMachineSinceLastCopy { get; set; } - - internal static bool CheckClipboardEx(ByteArrayOrString data, bool isFilePath) - { - Logger.LogDebug($"{nameof(CheckClipboardEx)}: ShareClipboard = {Setting.Values.ShareClipboard}, TransferFile = {Setting.Values.TransferFile}, data = {data}."); - Logger.LogDebug($"{nameof(CheckClipboardEx)}: {nameof(Setting.Values.OneWayClipboardMode)} = {Setting.Values.OneWayClipboardMode}."); - - if (!Setting.Values.ShareClipboard) - { - return false; - } - - if (Common.RunWithNoAdminRight && Setting.Values.OneWayClipboardMode) - { - return false; - } - - if (GetTick() - LastClipboardEventTime < 1000) - { - Logger.LogDebug("GetTick() - lastClipboardEventTime < 1000"); - LastClipboardEventTime = GetTick(); - return false; - } - - LastClipboardEventTime = GetTick(); - - try - { - IsClipboardDataImage = false; - LastClipboardData = null; - LastDragDropFile = null; - GC.Collect(); - - string stringData = null; - byte[] byteData = null; - - if (data.IsByteArray) - { - byteData = data.GetByteArray(); - } - else - { - stringData = data.GetString(); - } - - if (stringData != null) - { - if (!HasSwitchedMachineSinceLastCopy) - { - if (lastClipboardObject is string lastStringData && lastStringData.Equals(stringData, StringComparison.OrdinalIgnoreCase)) - { - Logger.LogDebug("CheckClipboardEx: Same string data."); - return false; - } - } - - HasSwitchedMachineSinceLastCopy = false; - - if (isFilePath) - { - Logger.LogDebug("Clipboard contains FileDropList"); - - if (!Setting.Values.TransferFile) - { - Logger.LogDebug("TransferFile option is unchecked."); - return false; - } - - string filePath = stringData; - - _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => - { - if (File.Exists(filePath) || Directory.Exists(filePath)) - { - if (File.Exists(filePath) && new FileInfo(filePath).Length <= MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT) - { - Logger.LogDebug("Clipboard contains: " + filePath); - LastDragDropFile = filePath; - SendClipboardBeat(); - SetToggleIcon(new int[TOGGLE_ICONS_SIZE] { ICON_BIG_CLIPBOARD, -1, ICON_BIG_CLIPBOARD, -1 }); - } - else - { - if (Directory.Exists(filePath)) - { - Logger.LogDebug("Clipboard contains a directory: " + filePath); - LastDragDropFile = filePath; - SendClipboardBeat(); - } - else - { - LastDragDropFile = filePath + " - File too big (greater than 100MB), please drag and drop the file instead!"; - SendClipboardBeat(); - Logger.Log("Clipboard: File too big: " + filePath); - } - - SetToggleIcon(new int[TOGGLE_ICONS_SIZE] { ICON_ERROR, -1, ICON_ERROR, -1 }); - } - } - else - { - Logger.Log("CheckClipboardEx: File not found: " + filePath); - } - }); - } - else - { - byte[] texts = Common.GetBytesU(stringData); - - using MemoryStream ms = new(); - using (DeflateStream s = new(ms, CompressionMode.Compress, true)) - { - s.Write(texts, 0, texts.Length); - } - - Logger.LogDebug("Plain/Zip = " + texts.Length.ToString(CultureInfo.CurrentCulture) + "/" + - ms.Length.ToString(CultureInfo.CurrentCulture)); - - LastClipboardData = ms.GetBuffer(); - } - } - else if (byteData != null) - { - if (!HasSwitchedMachineSinceLastCopy) - { - if (lastClipboardObject is byte[] lastByteData && Enumerable.SequenceEqual(lastByteData, byteData)) - { - Logger.LogDebug("CheckClipboardEx: Same byte[] data."); - return false; - } - } - - HasSwitchedMachineSinceLastCopy = false; - - Logger.LogDebug("Clipboard contains image"); - IsClipboardDataImage = true; - LastClipboardData = byteData; - } - else - { - Logger.LogDebug("*** Clipboard contains something else!"); - return false; - } - - lastClipboardObject = data; - - if (LastClipboardData != null && LastClipboardData.Length > 0) - { - if (LastClipboardData.Length > MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP) - { - SendClipboardBeat(); - SetToggleIcon(new int[TOGGLE_ICONS_SIZE] { ICON_BIG_CLIPBOARD, -1, ICON_BIG_CLIPBOARD, -1 }); - } - else - { - SetToggleIcon(new int[TOGGLE_ICONS_SIZE] { ICON_SMALL_CLIPBOARD, -1, -1, -1 }); - SendClipboardDataUsingTCP(LastClipboardData, IsClipboardDataImage); - } - - return true; - } - } - catch (Exception e) - { - Logger.Log(e); - } - - return false; - } - - private static void SendClipboardDataUsingTCP(byte[] bytes, bool image) - { - if (Sk == null) - { - return; - } - - new Task(() => - { - // SuppressFlow fixes an issue on service mode, where the helper process can't get enough permissions to be started again. - // More details can be found on: https://github.com/microsoft/PowerToys/pull/36892 - using var asyncFlowControl = ExecutionContext.SuppressFlow(); - - System.Threading.Thread thread = Thread.CurrentThread; - thread.Name = $"{nameof(SendClipboardDataUsingTCP)}.{thread.ManagedThreadId}"; - Thread.UpdateThreads(thread); - int l = bytes.Length; - int index = 0; - int len; - DATA package = new(); - byte[] buf = new byte[PACKAGE_SIZE_EX]; - int dataStart = PACKAGE_SIZE_EX - DATA_SIZE; - - while (true) - { - if ((index + DATA_SIZE) > l) - { - len = l - index; - Array.Clear(buf, 0, PACKAGE_SIZE_EX); - } - else - { - len = DATA_SIZE; - } - - Array.Copy(bytes, index, buf, dataStart, len); - package.Bytes = buf; - - package.Type = image ? PackageType.ClipboardImage : PackageType.ClipboardText; - package.Des = ID.ALL; - SkSend(package, (uint)MachineID, false); - - index += DATA_SIZE; - if (index >= l) - { - break; - } - } - - package.Type = PackageType.ClipboardDataEnd; - package.Des = ID.ALL; - SkSend(package, (uint)MachineID, false); - }).Start(); - } - - internal static void ReceiveClipboardDataUsingTCP(DATA data, bool image, TcpSk tcp) - { - try - { - if (Sk == null || RunOnLogonDesktop || RunOnScrSaverDesktop) - { - return; - } - - MemoryStream m = new(); - int dataStart = PACKAGE_SIZE_EX - DATA_SIZE; - m.Write(data.Bytes, dataStart, DATA_SIZE); - int unexpectedCount = 0; - - bool done = false; - do - { - data = SocketStuff.TcpReceiveData(tcp, out int err); - - switch (data.Type) - { - case PackageType.ClipboardImage: - case PackageType.ClipboardText: - m.Write(data.Bytes, dataStart, DATA_SIZE); - break; - - case PackageType.ClipboardDataEnd: - done = true; - break; - - default: - Receiver.ProcessPackage(data, tcp); - if (++unexpectedCount > 100) - { - Logger.Log("ReceiveClipboardDataUsingTCP: unexpectedCount > 100!"); - done = true; - } - - break; - } - } - while (!done); - - LastClipboardEventTime = GetTick(); - - if (image) - { - Image im = Image.FromStream(m); - Clipboard.SetImage(im); - LastClipboardEventTime = GetTick(); - } - else - { - Common.SetClipboardData(m.GetBuffer()); - LastClipboardEventTime = GetTick(); - } - - m.Dispose(); - - SetToggleIcon(new int[TOGGLE_ICONS_SIZE] { ICON_SMALL_CLIPBOARD, -1, ICON_SMALL_CLIPBOARD, -1 }); - } - catch (Exception e) - { - Logger.Log("ReceiveClipboardDataUsingTCP: " + e.Message); - } - } - - private static readonly Lock ClipboardThreadOldLock = new(); - private static System.Threading.Thread clipboardThreadOld; - - internal static void GetRemoteClipboard(string postAction) - { - if (!RunOnLogonDesktop && !RunOnScrSaverDesktop) - { - if (Common.LastMachineWithClipboardData == null || - Common.LastMachineWithClipboardData.Length < 1) - { - return; - } - - new Task(() => - { - // SuppressFlow fixes an issue on service mode, where the helper process can't get enough permissions to be started again. - // More details can be found on: https://github.com/microsoft/PowerToys/pull/36892 - using var asyncFlowControl = ExecutionContext.SuppressFlow(); - - System.Threading.Thread thread = Thread.CurrentThread; - thread.Name = $"{nameof(ConnectAndGetData)}.{thread.ManagedThreadId}"; - Thread.UpdateThreads(thread); - ConnectAndGetData(postAction); - }).Start(); - } - } - - private static Stream m; - - private static void ConnectAndGetData(object postAction) - { - if (Sk == null) - { - Logger.Log("ConnectAndGetData: Sk == null!"); - return; - } - - string remoteMachine; - TcpClient clipboardTcpClient = null; - string postAct = (string)postAction; - - Logger.LogDebug("ConnectAndGetData.postAction: " + postAct); - - ClipboardPostAction clipboardPostAct = postAct.Contains("mspaint,") ? ClipboardPostAction.Mspaint - : postAct.Equals("desktop", StringComparison.OrdinalIgnoreCase) ? ClipboardPostAction.Desktop - : ClipboardPostAction.Other; - - try - { - remoteMachine = postAct.Contains("mspaint,") ? postAct.Split(Comma)[1] : Common.LastMachineWithClipboardData; - - remoteMachine = remoteMachine.Trim(); - - if (!IsConnectedByAClientSocketTo(remoteMachine)) - { - Logger.Log($"No potential inbound connection from {MachineName} to {remoteMachine}, ask for a push back instead."); - ID machineId = MachineStuff.MachinePool.ResolveID(remoteMachine); - - if (machineId != ID.NONE) - { - SkSend( - new DATA() - { - Type = PackageType.ClipboardAsk, - Des = machineId, - MachineName = MachineName, - PostAction = clipboardPostAct, - }, - null, - false); - } - else - { - Logger.Log($"Unable to resolve {remoteMachine} to its long IP."); - } - - return; - } - - ShowToolTip("Connecting to " + remoteMachine, 2000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus); - - clipboardTcpClient = ConnectToRemoteClipboardSocket(remoteMachine); - } - catch (ThreadAbortException) - { - Logger.Log("The current thread is being aborted (1)."); - if (clipboardTcpClient != null && clipboardTcpClient.Connected) - { - clipboardTcpClient.Client.Close(); - } - - return; - } - catch (Exception e) - { - Logger.Log(e); - Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] - { - Common.ICON_BIG_CLIPBOARD, - -1, Common.ICON_BIG_CLIPBOARD, -1, - }); - ShowToolTip(e.Message, 1000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus); - return; - } - - bool clientPushData = false; - - if (!ShakeHand(ref remoteMachine, clipboardTcpClient.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref clipboardPostAct)) - { - return; - } - - ReceiveAndProcessClipboardData(remoteMachine, clipboardTcpClient.Client, enStream, deStream, postAct); - } - - internal static void ReceiveAndProcessClipboardData(string remoteMachine, Socket s, Stream enStream, Stream deStream, string postAct) - { - lock (ClipboardThreadOldLock) - { - // Do not enable two connections at the same time. - if (clipboardThreadOld != null && clipboardThreadOld.ThreadState != System.Threading.ThreadState.AbortRequested - && clipboardThreadOld.ThreadState != System.Threading.ThreadState.Aborted && clipboardThreadOld.IsAlive - && clipboardThreadOld.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) - { - if (clipboardThreadOld.Join(3000)) - { - if (m != null) - { - m.Flush(); - m.Close(); - m = null; - } - } - } - - clipboardThreadOld = Thread.CurrentThread; - } - - try - { - byte[] header = new byte[1024]; - byte[] buf = new byte[NETWORK_STREAM_BUF_SIZE]; - string fileName = null; - string tempFile = "data", savingFolder = string.Empty; - Common.ToggleIconsIndex = 0; - int rv; - long receivedCount = 0; - - if ((rv = deStream.ReadEx(header, 0, header.Length)) < header.Length) - { - Logger.Log("Reading header failed: " + rv.ToString(CultureInfo.CurrentCulture)); - Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] - { - Common.ICON_BIG_CLIPBOARD, - -1, -1, -1, - }); - return; - } - - fileName = Common.GetStringU(header).Replace("\0", string.Empty); - Logger.LogDebug("Header: " + fileName); - string[] headers = fileName.Split(Star); - - if (headers.Length < 2 || !long.TryParse(headers[0], out long dataSize)) - { - Logger.Log(string.Format( - CultureInfo.CurrentCulture, - "Reading header failed: {0}:{1}", - headers.Length, - fileName)); - Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] - { - Common.ICON_BIG_CLIPBOARD, - -1, -1, -1, - }); - return; - } - - fileName = headers[1]; - - Logger.LogDebug(string.Format( - CultureInfo.CurrentCulture, - "Receiving {0}:{1} from {2}...", - Path.GetFileName(fileName), - dataSize, - remoteMachine)); - ShowToolTip( - string.Format( - CultureInfo.CurrentCulture, - "Receiving {0} from {1}...", - Path.GetFileName(fileName), - remoteMachine), - 5000, - ToolTipIcon.Info, - Setting.Values.ShowClipNetStatus); - if (fileName.StartsWith("image", StringComparison.CurrentCultureIgnoreCase) || - fileName.StartsWith("text", StringComparison.CurrentCultureIgnoreCase)) - { - m = new MemoryStream(); - } - else - { - if (postAct.Equals("desktop", StringComparison.OrdinalIgnoreCase)) - { - _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => - { - savingFolder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\MouseWithoutBorders\\"; - - if (!Directory.Exists(savingFolder)) - { - _ = Directory.CreateDirectory(savingFolder); - } - }); - - tempFile = savingFolder + Path.GetFileName(fileName); - m = new FileStream(tempFile, FileMode.Create); - } - else if (postAct.Contains("mspaint")) - { - tempFile = GetMyStorageDir() + @"ScreenCapture-" + - remoteMachine + ".png"; - m = new FileStream(tempFile, FileMode.Create); - } - else - { - tempFile = GetMyStorageDir(); - tempFile += Path.GetFileName(fileName); - m = new FileStream(tempFile, FileMode.Create); - } - - Logger.Log("==> " + tempFile); - } - - ShowToolTip( - string.Format( - CultureInfo.CurrentCulture, - "Receiving {0} from {1}...", - Path.GetFileName(fileName), - remoteMachine), - 5000, - ToolTipIcon.Info, - Setting.Values.ShowClipNetStatus); - - do - { - rv = deStream.ReadEx(buf, 0, buf.Length); - - if (rv > 0) - { - receivedCount += rv; - - if (receivedCount > dataSize) - { - rv -= (int)(receivedCount - dataSize); - } - - m.Write(buf, 0, rv); - } - - if (Common.ToggleIcons == null) - { - Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] - { - Common.ICON_SMALL_CLIPBOARD, - -1, Common.ICON_SMALL_CLIPBOARD, -1, - }); - } - - string text = string.Format(CultureInfo.CurrentCulture, "{0}KB received: {1}", m.Length / 1024, Path.GetFileName(fileName)); - - DoSomethingInUIThread(() => - { - MainForm.SetTrayIconText(text); - }); - } - while (rv > 0); - - if (m != null && fileName != null) - { - m.Flush(); - Logger.LogDebug(m.Length.ToString(CultureInfo.CurrentCulture) + " bytes received."); - Common.LastClipboardEventTime = Common.GetTick(); - string toolTipText = null; - string sizeText = m.Length >= 1024 - ? (m.Length / 1024).ToString(CultureInfo.CurrentCulture) + "KB" - : m.Length.ToString(CultureInfo.CurrentCulture) + "Bytes"; - - PowerToysTelemetry.Log.WriteEvent(new MouseWithoutBorders.Telemetry.MouseWithoutBordersClipboardFileTransferEvent()); - - if (fileName.StartsWith("image", StringComparison.CurrentCultureIgnoreCase)) - { - Clipboard.SetImage(Image.FromStream(m)); - toolTipText = string.Format( - CultureInfo.CurrentCulture, - "{0} {1} from {2} is in Clipboard.", - sizeText, - "image", - remoteMachine); - } - else if (fileName.StartsWith("text", StringComparison.CurrentCultureIgnoreCase)) - { - byte[] data = (m as MemoryStream).GetBuffer(); - toolTipText = string.Format( - CultureInfo.CurrentCulture, - "{0} {1} from {2} is in Clipboard.", - sizeText, - "text", - remoteMachine); - Common.SetClipboardData(data); - } - else if (tempFile != null) - { - if (postAct.Equals("desktop", StringComparison.OrdinalIgnoreCase)) - { - toolTipText = string.Format( - CultureInfo.CurrentCulture, - "{0} {1} received from {2}!", - sizeText, - Path.GetFileName(fileName), - remoteMachine); - - _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => - { - ProcessStartInfo startInfo = new(); - startInfo.UseShellExecute = true; - startInfo.WorkingDirectory = savingFolder; - startInfo.FileName = savingFolder; - startInfo.Verb = "open"; - _ = Process.Start(startInfo); - }); - } - else if (postAct.Contains("mspaint")) - { - m.Close(); - m = null; - OpenImage(tempFile); - toolTipText = string.Format( - CultureInfo.CurrentCulture, - "{0} {1} from {2} is in Mspaint.", - sizeText, - Path.GetFileName(tempFile), - remoteMachine); - } - else - { - StringCollection filePaths = new() - { - tempFile, - }; - Clipboard.SetFileDropList(filePaths); - toolTipText = string.Format( - CultureInfo.CurrentCulture, - "{0} {1} from {2} is in Clipboard.", - sizeText, - Path.GetFileName(fileName), - remoteMachine); - } - } - - if (!string.IsNullOrWhiteSpace(toolTipText)) - { - Common.ShowToolTip(toolTipText, 5000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus); - } - - DoSomethingInUIThread(() => - { - MainForm.UpdateNotifyIcon(); - }); - - m?.Close(); - m = null; - } - } - catch (ThreadAbortException) - { - Logger.Log("The current thread is being aborted (3)."); - s.Close(); - - if (m != null) - { - m.Close(); - m = null; - } - - return; - } - catch (Exception e) - { - if (e is IOException) - { - string log = $"{nameof(ReceiveAndProcessClipboardData)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)"; - Logger.Log(log); - } - else - { - Logger.Log(e); - } - - Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] - { - Common.ICON_BIG_CLIPBOARD, - -1, Common.ICON_BIG_CLIPBOARD, -1, - }); - ShowToolTip(e.Message, 1000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus); - - if (m != null) - { - m.Close(); - m = null; - } - - return; - } - - s.Close(); - } - - internal static bool ShakeHand(ref string remoteName, Socket s, out Stream enStream, out Stream deStream, ref bool clientPushData, ref ClipboardPostAction postAction) - { - const int CLIPBOARD_HANDSHAKE_TIMEOUT = 30; - s.ReceiveTimeout = CLIPBOARD_HANDSHAKE_TIMEOUT * 1000; - s.NoDelay = true; - s.SendBufferSize = s.ReceiveBufferSize = 1024000; - - bool handShaken = false; - enStream = deStream = null; - - try - { - DATA package = new() - { - Type = clientPushData ? PackageType.ClipboardPush : PackageType.Clipboard, - PostAction = postAction, - Src = MachineID, - MachineName = MachineName, - }; - - byte[] buf = new byte[PACKAGE_SIZE_EX]; - - NetworkStream ns = new(s); - enStream = Common.GetEncryptedStream(ns); - Common.SendOrReceiveARandomDataBlockPerInitialIV(enStream); - Logger.LogDebug($"{nameof(ShakeHand)}: Writing header package."); - enStream.Write(package.Bytes, 0, PACKAGE_SIZE_EX); - - Logger.LogDebug($"{nameof(ShakeHand)}: Sent: clientPush={clientPushData}, postAction={postAction}."); - - deStream = Common.GetDecryptedStream(ns); - Common.SendOrReceiveARandomDataBlockPerInitialIV(deStream, false); - - Logger.LogDebug($"{nameof(ShakeHand)}: Reading header package."); - - int bytesReceived = deStream.ReadEx(buf, 0, Common.PACKAGE_SIZE_EX); - package.Bytes = buf; - - string name = "Unknown"; - - if (bytesReceived == Common.PACKAGE_SIZE_EX) - { - if (package.Type is PackageType.Clipboard or PackageType.ClipboardPush) - { - name = remoteName = package.MachineName; - - Logger.LogDebug($"{nameof(ShakeHand)}: Connection from {name}:{package.Src}"); - - if (MachineStuff.MachinePool.ResolveID(name) == package.Src && Common.IsConnectedTo(package.Src)) - { - clientPushData = package.Type == PackageType.ClipboardPush; - postAction = package.PostAction; - handShaken = true; - Logger.LogDebug($"{nameof(ShakeHand)}: Received: clientPush={clientPushData}, postAction={postAction}."); - } - else - { - Logger.LogDebug($"{nameof(ShakeHand)}: No active connection to the machine: {name}."); - } - } - else - { - Logger.LogDebug($"{nameof(ShakeHand)}: Unexpected package type: {package.Type}."); - } - } - else - { - Logger.LogDebug($"{nameof(ShakeHand)}: BytesTransferred != PACKAGE_SIZE_EX: {bytesReceived}"); - } - - if (!handShaken) - { - string msg = $"Clipboard connection rejected: {name}:{remoteName}/{package.Src}\r\n\r\nMake sure you run the same version in all machines."; - Logger.Log(msg); - Common.ShowToolTip(msg, 3000, ToolTipIcon.Warning); - Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_BIG_CLIPBOARD, -1, -1, -1 }); - } - } - catch (ThreadAbortException) - { - Logger.Log($"{nameof(ShakeHand)}: The current thread is being aborted."); - s.Close(); - } - catch (Exception e) - { - if (e is IOException) - { - string log = $"{nameof(ShakeHand)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)"; - Logger.Log(log); - } - else - { - Logger.Log(e); - } - - Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] - { - Common.ICON_BIG_CLIPBOARD, - -1, Common.ICON_BIG_CLIPBOARD, -1, - }); - MainForm.UpdateNotifyIcon(); - ShowToolTip(e.Message + "\r\n\r\nMake sure you run the same version in all machines.", 1000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus); - - if (m != null) - { - m.Close(); - m = null; - } - } - - return handShaken; - } - - internal static TcpClient ConnectToRemoteClipboardSocket(string remoteMachine) - { - TcpClient clipboardTcpClient; - clipboardTcpClient = new TcpClient(AddressFamily.InterNetworkV6); - clipboardTcpClient.Client.DualMode = true; - - SocketStuff sk = Common.Sk; - - if (sk != null) - { - Common.DoSomethingInUIThread(() => Common.MainForm.ChangeIcon(Common.ICON_SMALL_CLIPBOARD)); - - System.Net.IPAddress ip = GetConnectedClientSocketIPAddressFor(remoteMachine); - Logger.LogDebug($"{nameof(ConnectToRemoteClipboardSocket)}Connecting to {remoteMachine}:{ip}:{sk.TcpPort}..."); - - if (ip != null) - { - clipboardTcpClient.Connect(ip, sk.TcpPort); - } - else - { - clipboardTcpClient.Connect(remoteMachine, sk.TcpPort); - } - - Logger.LogDebug($"Connected from {clipboardTcpClient.Client.LocalEndPoint}. Getting data..."); - return clipboardTcpClient; - } - else - { - throw new ExpectedSocketException($"{nameof(ConnectToRemoteClipboardSocket)}: No longer connected."); - } - } - - internal static void SetClipboardData(byte[] data) - { - if (data == null || data.Length <= 0) - { - Logger.Log("data is null or empty!"); - return; - } - - if (data.Length > 1024000) - { - ShowToolTip( - string.Format( - CultureInfo.CurrentCulture, - "Decompressing {0} clipboard data ...", - (data.Length / 1024).ToString(CultureInfo.CurrentCulture) + "KB"), - 5000, - ToolTipIcon.Info, - Setting.Values.ShowClipNetStatus); - } - - string st = string.Empty; - - using (MemoryStream ms = new(data)) - { - using DeflateStream s = new(ms, CompressionMode.Decompress, true); - const int BufferSize = 1024000; // Buffer size should be big enough, this is critical to performance! - - int rv = 0; - - do - { - byte[] buffer = new byte[BufferSize]; - rv = s.ReadEx(buffer, 0, BufferSize); - - if (rv > 0) - { - st += Common.GetStringU(buffer); - } - else - { - break; - } - } - while (true); - } - - int textTypeCount = 0; - string[] texts = st.Split(new string[] { TEXT_TYPE_SEP }, StringSplitOptions.RemoveEmptyEntries); - string tmp; - DataObject data1 = new(); - - foreach (string txt in texts) - { - if (string.IsNullOrEmpty(txt.Trim(NullSeparator))) - { - continue; - } - - tmp = txt[3..]; - - if (txt.StartsWith("RTF", StringComparison.CurrentCultureIgnoreCase)) - { - Logger.LogDebug(((double)tmp.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of RTF <-"); - data1.SetData(DataFormats.Rtf, tmp); - } - else if (txt.StartsWith("HTM", StringComparison.CurrentCultureIgnoreCase)) - { - Logger.LogDebug(((double)tmp.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of HTM <-"); - data1.SetData(DataFormats.Html, tmp); - } - else if (txt.StartsWith("TXT", StringComparison.CurrentCultureIgnoreCase)) - { - Logger.LogDebug(((double)tmp.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of TXT <-"); - data1.SetData(DataFormats.UnicodeText, tmp); - } - else - { - if (textTypeCount == 0) - { - Logger.LogDebug(((double)txt.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of UNI <-"); - data1.SetData(DataFormats.UnicodeText, txt); - } - - Logger.Log("Invalid clipboard format received!"); - } - - textTypeCount++; - } - - if (textTypeCount > 0) - { - Clipboard.SetDataObject(data1); - } - } - } - - internal static class Clipboard - { - public static void SetFileDropList(StringCollection filePaths) - { - Common.DoSomethingInUIThread(() => - { - try - { - _ = Common.Retry( - nameof(SystemClipboard.SetFileDropList), - () => - { - SystemClipboard.SetFileDropList(filePaths); - return true; - }, - (log) => Logger.TelemetryLogTrace( - log, - SeverityLevel.Information), - () => Common.LastClipboardEventTime = Common.GetTick()); - } - catch (ExternalException e) - { - Logger.Log(e); - } - catch (ThreadStateException e) - { - Logger.Log(e); - } - catch (ArgumentNullException e) - { - Logger.Log(e); - } - catch (ArgumentException e) - { - Logger.Log(e); - } - }); - } - - public static void SetImage(Image image) - { - Common.DoSomethingInUIThread(() => - { - try - { - _ = Common.Retry( - nameof(SystemClipboard.SetImage), - () => - { - SystemClipboard.SetImage(image); - return true; - }, - (log) => Logger.TelemetryLogTrace(log, SeverityLevel.Information), - () => Common.LastClipboardEventTime = Common.GetTick()); - } - catch (ExternalException e) - { - Logger.Log(e); - } - catch (ThreadStateException e) - { - Logger.Log(e); - } - catch (ArgumentNullException e) - { - Logger.Log(e); - } - }); - } - - public static void SetText(string text) - { - Common.DoSomethingInUIThread(() => - { - try - { - _ = Common.Retry( - nameof(SystemClipboard.SetText), - () => - { - SystemClipboard.SetText(text); - return true; - }, - (log) => Logger.TelemetryLogTrace(log, SeverityLevel.Information), - () => Common.LastClipboardEventTime = Common.GetTick()); - } - catch (ExternalException e) - { - Logger.Log(e); - } - catch (ThreadStateException e) - { - Logger.Log(e); - } - catch (ArgumentNullException e) - { - Logger.Log(e); - } - }); - } - - public static void SetDataObject(DataObject dataObject) - { - Common.DoSomethingInUIThread(() => - { - try - { - SystemClipboard.SetDataObject(dataObject, true, 10, 200); - } - catch (ExternalException e) - { - string dataFormats = string.Join(",", dataObject.GetFormats()); - Logger.Log($"{e.Message}: {dataFormats}"); - } - catch (ThreadStateException e) - { - Logger.Log(e); - } - catch (ArgumentNullException e) - { - Logger.Log(e); - } - }); - } - } -} diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.InitAndCleanup.cs b/src/modules/MouseWithoutBorders/App/Class/Common.InitAndCleanup.cs deleted file mode 100644 index 44861926e9..0000000000 --- a/src/modules/MouseWithoutBorders/App/Class/Common.InitAndCleanup.cs +++ /dev/null @@ -1,284 +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.Globalization; -using System.Linq; -using System.Net.NetworkInformation; -using System.Security.Cryptography; -using System.Threading; - -// -// Initialization and clean up. -// -// -// 2008 created by Truong Do (ductdo). -// 2009-... modified by Truong Do (TruongDo). -// 2023- Included in PowerToys. -// -using Microsoft.Win32; -using MouseWithoutBorders.Class; -using MouseWithoutBorders.Core; -using MouseWithoutBorders.Form; -using Windows.UI.Input.Preview.Injection; - -using Thread = MouseWithoutBorders.Core.Thread; - -namespace MouseWithoutBorders -{ - internal partial class Common - { - private static bool initDone; - internal static int REOPEN_WHEN_WSAECONNRESET = -10054; - internal static int REOPEN_WHEN_HOTKEY = -10055; - internal static int PleaseReopenSocket; - internal static bool ReopenSocketDueToReadError; - - internal static DateTime LastResumeSuspendTime { get; set; } = DateTime.UtcNow; - - internal static bool InitDone - { - get => Common.initDone; - set => Common.initDone = value; - } - - internal static void UpdateMachineTimeAndID() - { - Common.MachineName = Common.MachineName.Trim(); - _ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true); - } - - private static void InitializeMachinePoolFromSettings() - { - try - { - MachineInf[] info = MachinePoolHelpers.LoadMachineInfoFromMachinePoolStringSetting(Setting.Values.MachinePoolString); - for (int i = 0; i < info.Length; i++) - { - info[i].Name = info[i].Name.Trim(); - } - - MachineStuff.MachinePool.Initialize(info); - MachineStuff.MachinePool.ResetIPAddressesForDeadMachines(true); - } - catch (Exception ex) - { - Logger.Log(ex); - MachineStuff.MachinePool.Clear(); - } - } - - internal static void SetupMachineNameAndID() - { - try - { - GetMachineName(); - DesMachineID = MachineStuff.NewDesMachineID = MachineID; - - // MessageBox.Show(machineID.ToString(CultureInfo.CurrentCulture)); // For test - InitializeMachinePoolFromSettings(); - - Common.MachineName = Common.MachineName.Trim(); - _ = MachineStuff.MachinePool.LearnMachine(Common.MachineName); - _ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true); - - MachineStuff.UpdateMachinePoolStringSetting(); - } - catch (Exception e) - { - Logger.Log(e); - } - } - - internal static void Init() - { - _ = Helper.GetUserName(); - Common.GeneratedKey = true; - - try - { - Common.MyKey = Setting.Values.MyKey; - int tmp = Setting.Values.MyKeyDaysToExpire; - } - catch (FormatException e) - { - Common.KeyCorrupted = true; - Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); - Logger.Log(e.Message); - } - catch (CryptographicException e) - { - Common.KeyCorrupted = true; - Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); - Logger.Log(e.Message); - } - - try - { - InputSimulation.Injector = InputInjector.TryCreate(); - if (InputSimulation.Injector != null) - { - InputSimulation.MoveMouseRelative(0, 0); - NativeMethods.InjectMouseInputAvailable = true; - } - } - catch (EntryPointNotFoundException) - { - NativeMethods.InjectMouseInputAvailable = false; - Logger.Log($"{nameof(NativeMethods.InjectMouseInputAvailable)} = false"); - } - - bool dummy = Setting.Values.DrawMouseEx; - Is64bitOS = IntPtr.Size == 8; - tcpPort = Setting.Values.TcpPort; - GetScreenConfig(); - PackageSent = new PackageMonitor(0); - PackageReceived = new PackageMonitor(0); - SetupMachineNameAndID(); - InitEncryption(); - CreateHelperThreads(); - - SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged); - NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged); - SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged); - PleaseReopenSocket = 9; - /* TODO: Telemetry for the matrix? */ - } - - private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) - { - Helper.WndProcCounter++; - - if (e.Mode is PowerModes.Resume or PowerModes.Suspend) - { - Logger.TelemetryLogTrace($"{nameof(SystemEvents_PowerModeChanged)}: {e.Mode}", SeverityLevel.Information); - LastResumeSuspendTime = DateTime.UtcNow; - MachineStuff.SwitchToMultipleMode(false, true); - } - } - - private static void CreateHelperThreads() - { - // NOTE(@yuyoyuppe): service crashes while trying to obtain this info, disabling. - /* - Thread watchDogThread = new(new ThreadStart(WatchDogThread), nameof(WatchDogThread)); - watchDogThread.Priority = ThreadPriority.Highest; - watchDogThread.Start(); - */ - - helper = new Thread(new ThreadStart(Helper.HelperThread), "Helper Thread"); - helper.SetApartmentState(ApartmentState.STA); - helper.Start(); - } - - private static void AskHelperThreadsToExit(int waitTime) - { - Helper.signalHelperToExit = true; - Helper.signalWatchDogToExit = true; - _ = EvSwitch.Set(); - - int c = 0; - if (helper != null && c < waitTime) - { - while (Helper.signalHelperToExit) - { - Thread.Sleep(1); - } - - helper = null; - } - } - - internal static void Cleanup() - { - try - { - SendByeBye(); - - // UnhookClipboard(); - AskHelperThreadsToExit(500); - MainForm.NotifyIcon.Visible = false; - MainForm.NotifyIcon.Dispose(); - CloseAllFormsAndHooks(); - - DoSomethingInUIThread(() => - { - Sk?.Close(true); - }); - } - catch (Exception e) - { - Logger.Log(e); - } - } - - private static long lastReleaseAllKeysCall; - - internal static void ReleaseAllKeys() - { - if (Math.Abs(GetTick() - lastReleaseAllKeysCall) < 2000) - { - return; - } - - lastReleaseAllKeysCall = GetTick(); - - KEYBDDATA kd; - kd.dwFlags = (int)LLKHF.UP; - - VK[] keys = new VK[] - { - VK.LSHIFT, VK.LCONTROL, VK.LMENU, VK.LWIN, VK.RSHIFT, - VK.RCONTROL, VK.RMENU, VK.RWIN, VK.SHIFT, VK.MENU, VK.CONTROL, - }; - - Logger.LogDebug("***** ReleaseAllKeys has been called! *****:"); - - foreach (VK vk in keys) - { - if ((NativeMethods.GetAsyncKeyState((IntPtr)vk) & 0x8000) != 0) - { - Logger.LogDebug(vk.ToString() + " is down, release it..."); - Hook?.ResetLastSwitchKeys(); // Sticky key can turn ALL PC mode on (CtrlCtrlCtrl) - kd.wVk = (int)vk; - InputSimulation.SendKey(kd); - Hook?.ResetLastSwitchKeys(); - } - } - } - - private static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) - { - Logger.LogDebug("NetworkAvailabilityEventArgs.IsAvailable: " + e.IsAvailable.ToString(CultureInfo.InvariantCulture)); - Helper.WndProcCounter++; - ScheduleReopenSocketsDueToNetworkChanges(!e.IsAvailable); - } - - private static void ScheduleReopenSocketsDueToNetworkChanges(bool closeSockets = true) - { - if (closeSockets) - { - // Slept/hibernated machine may still have the sockets' status as Connected:( (unchanged) so it would not re-connect after a timeout when waking up. - // Closing the sockets when it is going to sleep/hibernate will trigger the reconnection faster when it wakes up. - DoSomethingInUIThread( - () => - { - SocketStuff s = Sk; - Sk = null; - s?.Close(false); - }, - true); - } - - if (!Common.IsMyDesktopActive()) - { - PleaseReopenSocket = 0; - } - else if (PleaseReopenSocket != 10) - { - PleaseReopenSocket = 10; - } - } - } -} diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.WinAPI.cs b/src/modules/MouseWithoutBorders/App/Class/Common.WinAPI.cs index ed56101930..ee2d99398c 100644 --- a/src/modules/MouseWithoutBorders/App/Class/Common.WinAPI.cs +++ b/src/modules/MouseWithoutBorders/App/Class/Common.WinAPI.cs @@ -36,7 +36,7 @@ namespace MouseWithoutBorders internal static string ActiveDesktop => Common.activeDesktop; - private static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) + internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) { GetScreenConfig(); } @@ -340,7 +340,7 @@ namespace MouseWithoutBorders Setting.Values.LastX = JUST_GOT_BACK_FROM_SCREEN_SAVER; if (cleanupIfExit) { - Common.Cleanup(); + InitAndCleanup.Cleanup(); } Process.GetCurrentProcess().KillProcess(); diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.cs b/src/modules/MouseWithoutBorders/App/Class/Common.cs index 0494a952fd..ba5a1655e0 100644 --- a/src/modules/MouseWithoutBorders/App/Class/Common.cs +++ b/src/modules/MouseWithoutBorders/App/Class/Common.cs @@ -33,6 +33,7 @@ using MouseWithoutBorders.Class; using MouseWithoutBorders.Core; using MouseWithoutBorders.Exceptions; +using Clipboard = MouseWithoutBorders.Core.Clipboard; using Thread = MouseWithoutBorders.Core.Thread; // Log is enough @@ -90,8 +91,8 @@ namespace MouseWithoutBorders private static FrmMatrix matrixForm; private static FrmInputCallback inputCallbackForm; private static FrmAbout aboutForm; - private static Thread helper; #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 @@ -121,7 +122,9 @@ namespace MouseWithoutBorders internal static int switchCount; #pragma warning restore SA1307 private static long lastReconnectByHotKeyTime; - private static int tcpPort; +#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; @@ -210,7 +213,7 @@ namespace MouseWithoutBorders internal static bool Is64bitOS { - get; private set; + get; set; // set { Common.is64bitOS = value; } } @@ -611,7 +614,7 @@ namespace MouseWithoutBorders } * */ - private static void SendByeBye() + internal static void SendByeBye() { Logger.LogDebug($"{nameof(SendByeBye)}"); SendPackage(ID.ALL, PackageType.ByeBye); @@ -725,7 +728,7 @@ namespace MouseWithoutBorders internal static void SendImage(string machine, string file) { - LastDragDropFile = file; + Clipboard.LastDragDropFile = file; // Send ClipboardCapture if (machine.Equals("All", StringComparison.OrdinalIgnoreCase)) @@ -744,7 +747,7 @@ namespace MouseWithoutBorders internal static void SendImage(ID src, string file) { - LastDragDropFile = file; + Clipboard.LastDragDropFile = file; // Send ClipboardCapture SendPackage(src, PackageType.ClipboardCapture); @@ -1291,7 +1294,7 @@ namespace MouseWithoutBorders }); } - private static string GetMyStorageDir() + internal static string GetMyStorageDir() { string st = string.Empty; diff --git a/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs b/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs index 9a52f69529..62360b4795 100644 --- a/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs +++ b/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs @@ -28,6 +28,7 @@ using MouseWithoutBorders.Core; using SystemClipboard = System.Windows.Forms.Clipboard; #if !MM_HELPER +using Clipboard = MouseWithoutBorders.Core.Clipboard; using Thread = MouseWithoutBorders.Core.Thread; #endif @@ -159,7 +160,7 @@ namespace MouseWithoutBorders public void SendClipboardData(ByteArrayOrString data, bool isFilePath) { - _ = Common.CheckClipboardEx(data, isFilePath); + _ = Clipboard.CheckClipboardEx(data, isFilePath); } } #endif diff --git a/src/modules/MouseWithoutBorders/App/Class/InputHook.cs b/src/modules/MouseWithoutBorders/App/Class/InputHook.cs index 33cbe77e89..d68b1a1584 100644 --- a/src/modules/MouseWithoutBorders/App/Class/InputHook.cs +++ b/src/modules/MouseWithoutBorders/App/Class/InputHook.cs @@ -579,7 +579,7 @@ namespace MouseWithoutBorders.Class { Common.ShowToolTip("Reconnecting...", 2000); Common.LastReconnectByHotKeyTime = Common.GetTick(); - Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY; + InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY; return false; } @@ -632,7 +632,7 @@ namespace MouseWithoutBorders.Class { // Common.DoSomethingInUIThread(delegate() { - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); } // ); diff --git a/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs b/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs index a991c7f64f..0bbd8014ae 100644 --- a/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs +++ b/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs @@ -407,7 +407,7 @@ namespace MouseWithoutBorders.Class { ResetModifiersState(Setting.Values.HotKeyLockMachine); eatKey = true; - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); _ = NativeMethods.LockWorkStation(); } } @@ -439,7 +439,7 @@ namespace MouseWithoutBorders.Class { ctrlDown = altDown = false; eatKey = true; - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); } break; @@ -449,7 +449,7 @@ namespace MouseWithoutBorders.Class { winDown = false; eatKey = true; - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); uint rv = NativeMethods.LockWorkStation(); Logger.LogDebug("LockWorkStation returned " + rv.ToString(CultureInfo.CurrentCulture)); } diff --git a/src/modules/MouseWithoutBorders/App/Class/Program.cs b/src/modules/MouseWithoutBorders/App/Class/Program.cs index 2fd8357e24..c139da46e9 100644 --- a/src/modules/MouseWithoutBorders/App/Class/Program.cs +++ b/src/modules/MouseWithoutBorders/App/Class/Program.cs @@ -235,7 +235,7 @@ namespace MouseWithoutBorders.Class _ = Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Application.SetCompatibleTextRenderingDefault(false); - Common.Init(); + InitAndCleanup.Init(); Core.Helper.WndProcCounter++; var formScreen = new FrmScreen(); @@ -314,7 +314,7 @@ namespace MouseWithoutBorders.Class MachineStuff.UpdateMachinePoolStringSetting(); SocketStuff.InvalidKeyFound = false; - Common.ReopenSocketDueToReadError = true; + InitAndCleanup.ReopenSocketDueToReadError = true; Common.ReopenSockets(true); MachineStuff.SendMachineMatrix(); @@ -340,7 +340,7 @@ namespace MouseWithoutBorders.Class public void Reconnect() { SocketStuff.InvalidKeyFound = false; - Common.ReopenSocketDueToReadError = true; + InitAndCleanup.ReopenSocketDueToReadError = true; Common.ReopenSockets(true); for (int i = 0; i < 10; i++) @@ -397,7 +397,7 @@ namespace MouseWithoutBorders.Class using var asyncFlowControl = ExecutionContext.SuppressFlow(); Common.InputCallbackThreadID = Thread.CurrentThread.ManagedThreadId; - while (!Common.InitDone) + while (!InitAndCleanup.InitDone) { Thread.Sleep(100); } diff --git a/src/modules/MouseWithoutBorders/App/Class/Setting.cs b/src/modules/MouseWithoutBorders/App/Class/Setting.cs index 30b99a97d0..c9d81f2049 100644 --- a/src/modules/MouseWithoutBorders/App/Class/Setting.cs +++ b/src/modules/MouseWithoutBorders/App/Class/Setting.cs @@ -118,7 +118,7 @@ namespace MouseWithoutBorders.Class if (shouldReopenSockets) { SocketStuff.InvalidKeyFound = false; - Common.ReopenSocketDueToReadError = true; + InitAndCleanup.ReopenSocketDueToReadError = true; Common.ReopenSockets(true); } diff --git a/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs b/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs index 8796f61dfb..c5241beddf 100644 --- a/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs +++ b/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs @@ -29,6 +29,7 @@ using MouseWithoutBorders.Core; // using MouseWithoutBorders.Exceptions; +using Clipboard = MouseWithoutBorders.Core.Clipboard; using Thread = MouseWithoutBorders.Core.Thread; [module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.SocketStuff.#SendData(System.Byte[])", Justification = "Dotnet port with style preservation")] @@ -281,7 +282,7 @@ namespace MouseWithoutBorders.Class * */ Common.GetMachineName(); // IPs might have been changed - Common.UpdateMachineTimeAndID(); + InitAndCleanup.UpdateMachineTimeAndID(); Logger.LogDebug("Creating sockets..."); @@ -308,7 +309,7 @@ namespace MouseWithoutBorders.Class { Logger.TelemetryLogTrace("Restarting the service dues to WSAEADDRINUSE.", SeverityLevel.Warning); Program.StartService(); - Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET; + InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET; } break; @@ -1248,7 +1249,7 @@ namespace MouseWithoutBorders.Class // WSAECONNRESET if (e is ExpectedSocketException se && se.ShouldReconnect) { - Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET; + InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET; Logger.Log($"MainTCPRoutine: {nameof(FlagReopenSocketIfNeeded)}"); } } @@ -1306,7 +1307,7 @@ namespace MouseWithoutBorders.Class } catch (ObjectDisposedException e) { - Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET; + InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET; UpdateTcpSockets(currentTcp, SocketStatus.ForceClosed); currentSocket.Close(); Logger.Log($"{nameof(MainTCPRoutine)}: The socket could have been closed/disposed by other threads: {e.Message}"); @@ -1353,10 +1354,10 @@ namespace MouseWithoutBorders.Class * In this case, we should give ONE try to reconnect. */ - if (Common.ReopenSocketDueToReadError) + if (InitAndCleanup.ReopenSocketDueToReadError) { - Common.PleaseReopenSocket = Common.REOPEN_WHEN_WSAECONNRESET; - Common.ReopenSocketDueToReadError = false; + InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_WSAECONNRESET; + InitAndCleanup.ReopenSocketDueToReadError = false; } break; @@ -1641,7 +1642,7 @@ namespace MouseWithoutBorders.Class bool clientPushData = true; ClipboardPostAction postAction = ClipboardPostAction.Other; - bool handShaken = Common.ShakeHand(ref remoteEndPoint, s, out Stream enStream, out Stream deStream, ref clientPushData, ref postAction); + bool handShaken = Clipboard.ShakeHand(ref remoteEndPoint, s, out Stream enStream, out Stream deStream, ref clientPushData, ref postAction); if (!handShaken) { @@ -1656,7 +1657,7 @@ namespace MouseWithoutBorders.Class if (clientPushData) { - Common.ReceiveAndProcessClipboardData(remoteEndPoint, s, enStream, deStream, $"{postAction}"); + Clipboard.ReceiveAndProcessClipboardData(remoteEndPoint, s, enStream, deStream, $"{postAction}"); } else { @@ -1680,23 +1681,23 @@ namespace MouseWithoutBorders.Class const int CLOSE_TIMEOUT = 10; byte[] header = new byte[1024]; string headerString = string.Empty; - if (Common.LastDragDropFile != null) + if (Clipboard.LastDragDropFile != null) { string fileName = null; if (!Launch.ImpersonateLoggedOnUserAndDoSomething(() => { - if (!File.Exists(Common.LastDragDropFile)) + if (!File.Exists(Clipboard.LastDragDropFile)) { - headerString = Directory.Exists(Common.LastDragDropFile) - ? $"{0}*{Common.LastDragDropFile} - Folder is not supported, zip it first!" - : Common.LastDragDropFile.Contains("- File too big") - ? $"{0}*{Common.LastDragDropFile}" - : $"{0}*{Common.LastDragDropFile} not found!"; + headerString = Directory.Exists(Clipboard.LastDragDropFile) + ? $"{0}*{Clipboard.LastDragDropFile} - Folder is not supported, zip it first!" + : Clipboard.LastDragDropFile.Contains("- File too big") + ? $"{0}*{Clipboard.LastDragDropFile}" + : $"{0}*{Clipboard.LastDragDropFile} not found!"; } else { - fileName = Common.LastDragDropFile; + fileName = Clipboard.LastDragDropFile; headerString = $"{new FileInfo(fileName).Length}*{fileName}"; } })) @@ -1739,11 +1740,11 @@ namespace MouseWithoutBorders.Class Logger.Log(log); } } - else if (!Common.IsClipboardDataImage && Common.LastClipboardData != null) + else if (!Clipboard.IsClipboardDataImage && Clipboard.LastClipboardData != null) { try { - byte[] data = Common.LastClipboardData; + byte[] data = Clipboard.LastClipboardData; headerString = $"{data.Length}*{"text"}"; Common.GetBytesU(headerString).CopyTo(header, 0); @@ -1773,9 +1774,9 @@ namespace MouseWithoutBorders.Class Logger.Log(log); } } - else if (Common.LastClipboardData != null && Common.LastClipboardData.Length > 0) + else if (Clipboard.LastClipboardData != null && Clipboard.LastClipboardData.Length > 0) { - byte[] data = Common.LastClipboardData; + byte[] data = Clipboard.LastClipboardData; headerString = $"{data.Length}*{"image"}"; Common.GetBytesU(headerString).CopyTo(header, 0); @@ -1984,8 +1985,8 @@ namespace MouseWithoutBorders.Class { tcp = null; Setting.Values.MachineId = Common.Ran.Next(); - Common.UpdateMachineTimeAndID(); - Common.PleaseReopenSocket = Common.REOPEN_WHEN_HOTKEY; + InitAndCleanup.UpdateMachineTimeAndID(); + InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY; Logger.TelemetryLogTrace("MachineID conflict.", SeverityLevel.Information); } diff --git a/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs new file mode 100644 index 0000000000..5840325941 --- /dev/null +++ b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs @@ -0,0 +1,1155 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +using Microsoft.PowerToys.Telemetry; +using MouseWithoutBorders.Class; +using MouseWithoutBorders.Exceptions; + +using SystemClipboard = System.Windows.Forms.Clipboard; + +// +// Clipboard related routines. +// +// +// 2008 created by Truong Do (ductdo). +// 2009-... modified by Truong Do (TruongDo). +// 2023- Included in PowerToys. +// +namespace MouseWithoutBorders.Core; + +internal static class Clipboard +{ + private static readonly char[] Comma = new char[] { ',' }; + private static readonly char[] Star = new char[] { '*' }; + private static readonly char[] NullSeparator = new char[] { '\0' }; + + internal const uint BIG_CLIPBOARD_DATA_TIMEOUT = 30000; + private const uint MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1024 * 1024; // 1MB + private const uint MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 100 * 1024 * 1024; // 100MB + private const int TEXT_HEADER_SIZE = 12; + private const int DATA_SIZE = 48; + private const string TEXT_TYPE_SEP = "{4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}"; + private static long lastClipboardEventTime; + private static string lastMachineWithClipboardData; + private static string lastDragDropFile; +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter + internal static long clipboardCopiedTime; +#pragma warning restore SA1307 + + internal static ID LastIDWithClipboardData { get; set; } + + internal static string LastDragDropFile + { + get => Clipboard.lastDragDropFile; + set => Clipboard.lastDragDropFile = value; + } + + internal static string LastMachineWithClipboardData + { + get => Clipboard.lastMachineWithClipboardData; + set => Clipboard.lastMachineWithClipboardData = value; + } + + private static long LastClipboardEventTime + { + get => Clipboard.lastClipboardEventTime; + set => Clipboard.lastClipboardEventTime = value; + } + + private static IntPtr NextClipboardViewer { get; set; } + + internal static bool IsClipboardDataImage { get; private set; } + + internal static byte[] LastClipboardData { get; private set; } + + private static object lastClipboardObject = string.Empty; + + internal static bool HasSwitchedMachineSinceLastCopy { get; set; } + + internal static bool CheckClipboardEx(ByteArrayOrString data, bool isFilePath) + { + Logger.LogDebug($"{nameof(CheckClipboardEx)}: ShareClipboard = {Setting.Values.ShareClipboard}, TransferFile = {Setting.Values.TransferFile}, data = {data}."); + Logger.LogDebug($"{nameof(CheckClipboardEx)}: {nameof(Setting.Values.OneWayClipboardMode)} = {Setting.Values.OneWayClipboardMode}."); + + if (!Setting.Values.ShareClipboard) + { + return false; + } + + if (Common.RunWithNoAdminRight && Setting.Values.OneWayClipboardMode) + { + return false; + } + + if (Common.GetTick() - LastClipboardEventTime < 1000) + { + Logger.LogDebug("GetTick() - lastClipboardEventTime < 1000"); + LastClipboardEventTime = Common.GetTick(); + return false; + } + + LastClipboardEventTime = Common.GetTick(); + + try + { + IsClipboardDataImage = false; + LastClipboardData = null; + LastDragDropFile = null; + GC.Collect(); + + string stringData = null; + byte[] byteData = null; + + if (data.IsByteArray) + { + byteData = data.GetByteArray(); + } + else + { + stringData = data.GetString(); + } + + if (stringData != null) + { + if (!HasSwitchedMachineSinceLastCopy) + { + if (lastClipboardObject is string lastStringData && lastStringData.Equals(stringData, StringComparison.OrdinalIgnoreCase)) + { + Logger.LogDebug("CheckClipboardEx: Same string data."); + return false; + } + } + + HasSwitchedMachineSinceLastCopy = false; + + if (isFilePath) + { + Logger.LogDebug("Clipboard contains FileDropList"); + + if (!Setting.Values.TransferFile) + { + Logger.LogDebug("TransferFile option is unchecked."); + return false; + } + + string filePath = stringData; + + _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => + { + if (File.Exists(filePath) || Directory.Exists(filePath)) + { + if (File.Exists(filePath) && new FileInfo(filePath).Length <= MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT) + { + Logger.LogDebug("Clipboard contains: " + filePath); + LastDragDropFile = filePath; + Common.SendClipboardBeat(); + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_BIG_CLIPBOARD, -1, Common.ICON_BIG_CLIPBOARD, -1 }); + } + else + { + if (Directory.Exists(filePath)) + { + Logger.LogDebug("Clipboard contains a directory: " + filePath); + LastDragDropFile = filePath; + Common.SendClipboardBeat(); + } + else + { + LastDragDropFile = filePath + " - File too big (greater than 100MB), please drag and drop the file instead!"; + Common.SendClipboardBeat(); + Logger.Log("Clipboard: File too big: " + filePath); + } + + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_ERROR, -1, Common.ICON_ERROR, -1 }); + } + } + else + { + Logger.Log("CheckClipboardEx: File not found: " + filePath); + } + }); + } + else + { + byte[] texts = Common.GetBytesU(stringData); + + using MemoryStream ms = new(); + using (DeflateStream s = new(ms, CompressionMode.Compress, true)) + { + s.Write(texts, 0, texts.Length); + } + + Logger.LogDebug("Plain/Zip = " + texts.Length.ToString(CultureInfo.CurrentCulture) + "/" + + ms.Length.ToString(CultureInfo.CurrentCulture)); + + LastClipboardData = ms.GetBuffer(); + } + } + else if (byteData != null) + { + if (!HasSwitchedMachineSinceLastCopy) + { + if (lastClipboardObject is byte[] lastByteData && Enumerable.SequenceEqual(lastByteData, byteData)) + { + Logger.LogDebug("CheckClipboardEx: Same byte[] data."); + return false; + } + } + + HasSwitchedMachineSinceLastCopy = false; + + Logger.LogDebug("Clipboard contains image"); + IsClipboardDataImage = true; + LastClipboardData = byteData; + } + else + { + Logger.LogDebug("*** Clipboard contains something else!"); + return false; + } + + lastClipboardObject = data; + + if (LastClipboardData != null && LastClipboardData.Length > 0) + { + if (LastClipboardData.Length > MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP) + { + Common.SendClipboardBeat(); + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_BIG_CLIPBOARD, -1, Common.ICON_BIG_CLIPBOARD, -1 }); + } + else + { + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_SMALL_CLIPBOARD, -1, -1, -1 }); + SendClipboardDataUsingTCP(LastClipboardData, IsClipboardDataImage); + } + + return true; + } + } + catch (Exception e) + { + Logger.Log(e); + } + + return false; + } + + private static void SendClipboardDataUsingTCP(byte[] bytes, bool image) + { + if (Common.Sk == null) + { + return; + } + + new Task(() => + { + // SuppressFlow fixes an issue on service mode, where the helper process can't get enough permissions to be started again. + // More details can be found on: https://github.com/microsoft/PowerToys/pull/36892 + using var asyncFlowControl = ExecutionContext.SuppressFlow(); + + System.Threading.Thread thread = Thread.CurrentThread; + thread.Name = $"{nameof(SendClipboardDataUsingTCP)}.{thread.ManagedThreadId}"; + Thread.UpdateThreads(thread); + int l = bytes.Length; + int index = 0; + int len; + DATA package = new(); + byte[] buf = new byte[Common.PACKAGE_SIZE_EX]; + int dataStart = Common.PACKAGE_SIZE_EX - DATA_SIZE; + + while (true) + { + if ((index + DATA_SIZE) > l) + { + len = l - index; + Array.Clear(buf, 0, Common.PACKAGE_SIZE_EX); + } + else + { + len = DATA_SIZE; + } + + Array.Copy(bytes, index, buf, dataStart, len); + package.Bytes = buf; + + package.Type = image ? PackageType.ClipboardImage : PackageType.ClipboardText; + package.Des = ID.ALL; + Common.SkSend(package, (uint)Common.MachineID, false); + + index += DATA_SIZE; + if (index >= l) + { + break; + } + } + + package.Type = PackageType.ClipboardDataEnd; + package.Des = ID.ALL; + Common.SkSend(package, (uint)Common.MachineID, false); + }).Start(); + } + + internal static void ReceiveClipboardDataUsingTCP(DATA data, bool image, TcpSk tcp) + { + try + { + if (Common.Sk == null || Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop) + { + return; + } + + MemoryStream m = new(); + int dataStart = Common.PACKAGE_SIZE_EX - DATA_SIZE; + m.Write(data.Bytes, dataStart, DATA_SIZE); + int unexpectedCount = 0; + + bool done = false; + do + { + data = SocketStuff.TcpReceiveData(tcp, out int err); + + switch (data.Type) + { + case PackageType.ClipboardImage: + case PackageType.ClipboardText: + m.Write(data.Bytes, dataStart, DATA_SIZE); + break; + + case PackageType.ClipboardDataEnd: + done = true; + break; + + default: + Receiver.ProcessPackage(data, tcp); + if (++unexpectedCount > 100) + { + Logger.Log("ReceiveClipboardDataUsingTCP: unexpectedCount > 100!"); + done = true; + } + + break; + } + } + while (!done); + + LastClipboardEventTime = Common.GetTick(); + + if (image) + { + Image im = Image.FromStream(m); + Clipboard.SetImage(im); + LastClipboardEventTime = Common.GetTick(); + } + else + { + Clipboard.SetClipboardData(m.GetBuffer()); + LastClipboardEventTime = Common.GetTick(); + } + + m.Dispose(); + + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_SMALL_CLIPBOARD, -1, Common.ICON_SMALL_CLIPBOARD, -1 }); + } + catch (Exception e) + { + Logger.Log("ReceiveClipboardDataUsingTCP: " + e.Message); + } + } + + private static readonly Lock ClipboardThreadOldLock = new(); + private static System.Threading.Thread clipboardThreadOld; + + internal static void GetRemoteClipboard(string postAction) + { + if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) + { + if (Clipboard.LastMachineWithClipboardData == null || + Clipboard.LastMachineWithClipboardData.Length < 1) + { + return; + } + + new Task(() => + { + // SuppressFlow fixes an issue on service mode, where the helper process can't get enough permissions to be started again. + // More details can be found on: https://github.com/microsoft/PowerToys/pull/36892 + using var asyncFlowControl = ExecutionContext.SuppressFlow(); + + System.Threading.Thread thread = Thread.CurrentThread; + thread.Name = $"{nameof(ConnectAndGetData)}.{thread.ManagedThreadId}"; + Thread.UpdateThreads(thread); + ConnectAndGetData(postAction); + }).Start(); + } + } + + private static Stream m; + + private static void ConnectAndGetData(object postAction) + { + if (Common.Sk == null) + { + Logger.Log("ConnectAndGetData: Sk == null!"); + return; + } + + string remoteMachine; + TcpClient clipboardTcpClient = null; + string postAct = (string)postAction; + + Logger.LogDebug("ConnectAndGetData.postAction: " + postAct); + + ClipboardPostAction clipboardPostAct = postAct.Contains("mspaint,") ? ClipboardPostAction.Mspaint + : postAct.Equals("desktop", StringComparison.OrdinalIgnoreCase) ? ClipboardPostAction.Desktop + : ClipboardPostAction.Other; + + try + { + remoteMachine = postAct.Contains("mspaint,") ? postAct.Split(Comma)[1] : Clipboard.LastMachineWithClipboardData; + + remoteMachine = remoteMachine.Trim(); + + if (!Common.IsConnectedByAClientSocketTo(remoteMachine)) + { + Logger.Log($"No potential inbound connection from {Common.MachineName} to {remoteMachine}, ask for a push back instead."); + ID machineId = MachineStuff.MachinePool.ResolveID(remoteMachine); + + if (machineId != ID.NONE) + { + Common.SkSend( + new DATA() + { + Type = PackageType.ClipboardAsk, + Des = machineId, + MachineName = Common.MachineName, + PostAction = clipboardPostAct, + }, + null, + false); + } + else + { + Logger.Log($"Unable to resolve {remoteMachine} to its long IP."); + } + + return; + } + + Common.ShowToolTip("Connecting to " + remoteMachine, 2000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus); + + clipboardTcpClient = ConnectToRemoteClipboardSocket(remoteMachine); + } + catch (ThreadAbortException) + { + Logger.Log("The current thread is being aborted (1)."); + if (clipboardTcpClient != null && clipboardTcpClient.Connected) + { + clipboardTcpClient.Client.Close(); + } + + return; + } + catch (Exception e) + { + Logger.Log(e); + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] + { + Common.ICON_BIG_CLIPBOARD, + -1, Common.ICON_BIG_CLIPBOARD, -1, + }); + Common.ShowToolTip(e.Message, 1000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus); + return; + } + + bool clientPushData = false; + + if (!ShakeHand(ref remoteMachine, clipboardTcpClient.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref clipboardPostAct)) + { + return; + } + + ReceiveAndProcessClipboardData(remoteMachine, clipboardTcpClient.Client, enStream, deStream, postAct); + } + + internal static void ReceiveAndProcessClipboardData(string remoteMachine, Socket s, Stream enStream, Stream deStream, string postAct) + { + lock (ClipboardThreadOldLock) + { + // Do not enable two connections at the same time. + if (clipboardThreadOld != null && clipboardThreadOld.ThreadState != System.Threading.ThreadState.AbortRequested + && clipboardThreadOld.ThreadState != System.Threading.ThreadState.Aborted && clipboardThreadOld.IsAlive + && clipboardThreadOld.ManagedThreadId != Thread.CurrentThread.ManagedThreadId) + { + if (clipboardThreadOld.Join(3000)) + { + if (m != null) + { + m.Flush(); + m.Close(); + m = null; + } + } + } + + clipboardThreadOld = Thread.CurrentThread; + } + + try + { + byte[] header = new byte[1024]; + byte[] buf = new byte[Common.NETWORK_STREAM_BUF_SIZE]; + string fileName = null; + string tempFile = "data", savingFolder = string.Empty; + Common.ToggleIconsIndex = 0; + int rv; + long receivedCount = 0; + + if ((rv = deStream.ReadEx(header, 0, header.Length)) < header.Length) + { + Logger.Log("Reading header failed: " + rv.ToString(CultureInfo.CurrentCulture)); + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] + { + Common.ICON_BIG_CLIPBOARD, + -1, -1, -1, + }); + return; + } + + fileName = Common.GetStringU(header).Replace("\0", string.Empty); + Logger.LogDebug("Header: " + fileName); + string[] headers = fileName.Split(Star); + + if (headers.Length < 2 || !long.TryParse(headers[0], out long dataSize)) + { + Logger.Log(string.Format( + CultureInfo.CurrentCulture, + "Reading header failed: {0}:{1}", + headers.Length, + fileName)); + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] + { + Common.ICON_BIG_CLIPBOARD, + -1, -1, -1, + }); + return; + } + + fileName = headers[1]; + + Logger.LogDebug(string.Format( + CultureInfo.CurrentCulture, + "Receiving {0}:{1} from {2}...", + Path.GetFileName(fileName), + dataSize, + remoteMachine)); + Common.ShowToolTip( + string.Format( + CultureInfo.CurrentCulture, + "Receiving {0} from {1}...", + Path.GetFileName(fileName), + remoteMachine), + 5000, + ToolTipIcon.Info, + Setting.Values.ShowClipNetStatus); + if (fileName.StartsWith("image", StringComparison.CurrentCultureIgnoreCase) || + fileName.StartsWith("text", StringComparison.CurrentCultureIgnoreCase)) + { + m = new MemoryStream(); + } + else + { + if (postAct.Equals("desktop", StringComparison.OrdinalIgnoreCase)) + { + _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => + { + savingFolder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\MouseWithoutBorders\\"; + + if (!Directory.Exists(savingFolder)) + { + _ = Directory.CreateDirectory(savingFolder); + } + }); + + tempFile = savingFolder + Path.GetFileName(fileName); + m = new FileStream(tempFile, FileMode.Create); + } + else if (postAct.Contains("mspaint")) + { + tempFile = Common.GetMyStorageDir() + @"ScreenCapture-" + + remoteMachine + ".png"; + m = new FileStream(tempFile, FileMode.Create); + } + else + { + tempFile = Common.GetMyStorageDir(); + tempFile += Path.GetFileName(fileName); + m = new FileStream(tempFile, FileMode.Create); + } + + Logger.Log("==> " + tempFile); + } + + Common.ShowToolTip( + string.Format( + CultureInfo.CurrentCulture, + "Receiving {0} from {1}...", + Path.GetFileName(fileName), + remoteMachine), + 5000, + ToolTipIcon.Info, + Setting.Values.ShowClipNetStatus); + + do + { + rv = deStream.ReadEx(buf, 0, buf.Length); + + if (rv > 0) + { + receivedCount += rv; + + if (receivedCount > dataSize) + { + rv -= (int)(receivedCount - dataSize); + } + + m.Write(buf, 0, rv); + } + + if (Common.ToggleIcons == null) + { + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] + { + Common.ICON_SMALL_CLIPBOARD, + -1, Common.ICON_SMALL_CLIPBOARD, -1, + }); + } + + string text = string.Format(CultureInfo.CurrentCulture, "{0}KB received: {1}", m.Length / 1024, Path.GetFileName(fileName)); + + Common.DoSomethingInUIThread(() => + { + Common.MainForm.SetTrayIconText(text); + }); + } + while (rv > 0); + + if (m != null && fileName != null) + { + m.Flush(); + Logger.LogDebug(m.Length.ToString(CultureInfo.CurrentCulture) + " bytes received."); + Clipboard.LastClipboardEventTime = Common.GetTick(); + string toolTipText = null; + string sizeText = m.Length >= 1024 + ? (m.Length / 1024).ToString(CultureInfo.CurrentCulture) + "KB" + : m.Length.ToString(CultureInfo.CurrentCulture) + "Bytes"; + + PowerToysTelemetry.Log.WriteEvent(new MouseWithoutBorders.Telemetry.MouseWithoutBordersClipboardFileTransferEvent()); + + if (fileName.StartsWith("image", StringComparison.CurrentCultureIgnoreCase)) + { + Clipboard.SetImage(Image.FromStream(m)); + toolTipText = string.Format( + CultureInfo.CurrentCulture, + "{0} {1} from {2} is in Clipboard.", + sizeText, + "image", + remoteMachine); + } + else if (fileName.StartsWith("text", StringComparison.CurrentCultureIgnoreCase)) + { + byte[] data = (m as MemoryStream).GetBuffer(); + toolTipText = string.Format( + CultureInfo.CurrentCulture, + "{0} {1} from {2} is in Clipboard.", + sizeText, + "text", + remoteMachine); + Clipboard.SetClipboardData(data); + } + else if (tempFile != null) + { + if (postAct.Equals("desktop", StringComparison.OrdinalIgnoreCase)) + { + toolTipText = string.Format( + CultureInfo.CurrentCulture, + "{0} {1} received from {2}!", + sizeText, + Path.GetFileName(fileName), + remoteMachine); + + _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() => + { + ProcessStartInfo startInfo = new(); + startInfo.UseShellExecute = true; + startInfo.WorkingDirectory = savingFolder; + startInfo.FileName = savingFolder; + startInfo.Verb = "open"; + _ = Process.Start(startInfo); + }); + } + else if (postAct.Contains("mspaint")) + { + m.Close(); + m = null; + Common.OpenImage(tempFile); + toolTipText = string.Format( + CultureInfo.CurrentCulture, + "{0} {1} from {2} is in Mspaint.", + sizeText, + Path.GetFileName(tempFile), + remoteMachine); + } + else + { + StringCollection filePaths = new() + { + tempFile, + }; + Clipboard.SetFileDropList(filePaths); + toolTipText = string.Format( + CultureInfo.CurrentCulture, + "{0} {1} from {2} is in Clipboard.", + sizeText, + Path.GetFileName(fileName), + remoteMachine); + } + } + + if (!string.IsNullOrWhiteSpace(toolTipText)) + { + Common.ShowToolTip(toolTipText, 5000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus); + } + + Common.DoSomethingInUIThread(() => + { + Common.MainForm.UpdateNotifyIcon(); + }); + + m?.Close(); + m = null; + } + } + catch (ThreadAbortException) + { + Logger.Log("The current thread is being aborted (3)."); + s.Close(); + + if (m != null) + { + m.Close(); + m = null; + } + + return; + } + catch (Exception e) + { + if (e is IOException) + { + string log = $"{nameof(ReceiveAndProcessClipboardData)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)"; + Logger.Log(log); + } + else + { + Logger.Log(e); + } + + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] + { + Common.ICON_BIG_CLIPBOARD, + -1, Common.ICON_BIG_CLIPBOARD, -1, + }); + Common.ShowToolTip(e.Message, 1000, ToolTipIcon.Info, Setting.Values.ShowClipNetStatus); + + if (m != null) + { + m.Close(); + m = null; + } + + return; + } + + s.Close(); + } + + internal static bool ShakeHand(ref string remoteName, Socket s, out Stream enStream, out Stream deStream, ref bool clientPushData, ref ClipboardPostAction postAction) + { + const int CLIPBOARD_HANDSHAKE_TIMEOUT = 30; + s.ReceiveTimeout = CLIPBOARD_HANDSHAKE_TIMEOUT * 1000; + s.NoDelay = true; + s.SendBufferSize = s.ReceiveBufferSize = 1024000; + + bool handShaken = false; + enStream = deStream = null; + + try + { + DATA package = new() + { + Type = clientPushData ? PackageType.ClipboardPush : PackageType.Clipboard, + PostAction = postAction, + Src = Common.MachineID, + MachineName = Common.MachineName, + }; + + byte[] buf = new byte[Common.PACKAGE_SIZE_EX]; + + NetworkStream ns = new(s); + enStream = Common.GetEncryptedStream(ns); + Common.SendOrReceiveARandomDataBlockPerInitialIV(enStream); + Logger.LogDebug($"{nameof(ShakeHand)}: Writing header package."); + enStream.Write(package.Bytes, 0, Common.PACKAGE_SIZE_EX); + + Logger.LogDebug($"{nameof(ShakeHand)}: Sent: clientPush={clientPushData}, postAction={postAction}."); + + deStream = Common.GetDecryptedStream(ns); + Common.SendOrReceiveARandomDataBlockPerInitialIV(deStream, false); + + Logger.LogDebug($"{nameof(ShakeHand)}: Reading header package."); + + int bytesReceived = deStream.ReadEx(buf, 0, Common.PACKAGE_SIZE_EX); + package.Bytes = buf; + + string name = "Unknown"; + + if (bytesReceived == Common.PACKAGE_SIZE_EX) + { + if (package.Type is PackageType.Clipboard or PackageType.ClipboardPush) + { + name = remoteName = package.MachineName; + + Logger.LogDebug($"{nameof(ShakeHand)}: Connection from {name}:{package.Src}"); + + if (MachineStuff.MachinePool.ResolveID(name) == package.Src && Common.IsConnectedTo(package.Src)) + { + clientPushData = package.Type == PackageType.ClipboardPush; + postAction = package.PostAction; + handShaken = true; + Logger.LogDebug($"{nameof(ShakeHand)}: Received: clientPush={clientPushData}, postAction={postAction}."); + } + else + { + Logger.LogDebug($"{nameof(ShakeHand)}: No active connection to the machine: {name}."); + } + } + else + { + Logger.LogDebug($"{nameof(ShakeHand)}: Unexpected package type: {package.Type}."); + } + } + else + { + Logger.LogDebug($"{nameof(ShakeHand)}: BytesTransferred != PACKAGE_SIZE_EX: {bytesReceived}"); + } + + if (!handShaken) + { + string msg = $"Clipboard connection rejected: {name}:{remoteName}/{package.Src}\r\n\r\nMake sure you run the same version in all machines."; + Logger.Log(msg); + Common.ShowToolTip(msg, 3000, ToolTipIcon.Warning); + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] { Common.ICON_BIG_CLIPBOARD, -1, -1, -1 }); + } + } + catch (ThreadAbortException) + { + Logger.Log($"{nameof(ShakeHand)}: The current thread is being aborted."); + s.Close(); + } + catch (Exception e) + { + if (e is IOException) + { + string log = $"{nameof(ShakeHand)}: Exception accessing the socket: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)"; + Logger.Log(log); + } + else + { + Logger.Log(e); + } + + Common.SetToggleIcon(new int[Common.TOGGLE_ICONS_SIZE] + { + Common.ICON_BIG_CLIPBOARD, + -1, Common.ICON_BIG_CLIPBOARD, -1, + }); + Common.MainForm.UpdateNotifyIcon(); + Common.ShowToolTip(e.Message + "\r\n\r\nMake sure you run the same version in all machines.", 1000, ToolTipIcon.Warning, Setting.Values.ShowClipNetStatus); + + if (m != null) + { + m.Close(); + m = null; + } + } + + return handShaken; + } + + internal static TcpClient ConnectToRemoteClipboardSocket(string remoteMachine) + { + TcpClient clipboardTcpClient; + clipboardTcpClient = new TcpClient(AddressFamily.InterNetworkV6); + clipboardTcpClient.Client.DualMode = true; + + SocketStuff sk = Common.Sk; + + if (sk != null) + { + Common.DoSomethingInUIThread(() => Common.MainForm.ChangeIcon(Common.ICON_SMALL_CLIPBOARD)); + + System.Net.IPAddress ip = Common.GetConnectedClientSocketIPAddressFor(remoteMachine); + Logger.LogDebug($"{nameof(ConnectToRemoteClipboardSocket)}Connecting to {remoteMachine}:{ip}:{sk.TcpPort}..."); + + if (ip != null) + { + clipboardTcpClient.Connect(ip, sk.TcpPort); + } + else + { + clipboardTcpClient.Connect(remoteMachine, sk.TcpPort); + } + + Logger.LogDebug($"Connected from {clipboardTcpClient.Client.LocalEndPoint}. Getting data..."); + return clipboardTcpClient; + } + else + { + throw new ExpectedSocketException($"{nameof(ConnectToRemoteClipboardSocket)}: No longer connected."); + } + } + + private static void SetClipboardData(byte[] data) + { + if (data == null || data.Length <= 0) + { + Logger.Log("data is null or empty!"); + return; + } + + if (data.Length > 1024000) + { + Common.ShowToolTip( + string.Format( + CultureInfo.CurrentCulture, + "Decompressing {0} clipboard data ...", + (data.Length / 1024).ToString(CultureInfo.CurrentCulture) + "KB"), + 5000, + ToolTipIcon.Info, + Setting.Values.ShowClipNetStatus); + } + + string st = string.Empty; + + using (MemoryStream ms = new(data)) + { + using DeflateStream s = new(ms, CompressionMode.Decompress, true); + const int BufferSize = 1024000; // Buffer size should be big enough, this is critical to performance! + + int rv = 0; + + do + { + byte[] buffer = new byte[BufferSize]; + rv = s.ReadEx(buffer, 0, BufferSize); + + if (rv > 0) + { + st += Common.GetStringU(buffer); + } + else + { + break; + } + } + while (true); + } + + int textTypeCount = 0; + string[] texts = st.Split(new string[] { TEXT_TYPE_SEP }, StringSplitOptions.RemoveEmptyEntries); + string tmp; + DataObject data1 = new(); + + foreach (string txt in texts) + { + if (string.IsNullOrEmpty(txt.Trim(NullSeparator))) + { + continue; + } + + tmp = txt[3..]; + + if (txt.StartsWith("RTF", StringComparison.CurrentCultureIgnoreCase)) + { + Logger.LogDebug(((double)tmp.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of RTF <-"); + data1.SetData(DataFormats.Rtf, tmp); + } + else if (txt.StartsWith("HTM", StringComparison.CurrentCultureIgnoreCase)) + { + Logger.LogDebug(((double)tmp.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of HTM <-"); + data1.SetData(DataFormats.Html, tmp); + } + else if (txt.StartsWith("TXT", StringComparison.CurrentCultureIgnoreCase)) + { + Logger.LogDebug(((double)tmp.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of TXT <-"); + data1.SetData(DataFormats.UnicodeText, tmp); + } + else + { + if (textTypeCount == 0) + { + Logger.LogDebug(((double)txt.Length / 1024).ToString("0.00", CultureInfo.InvariantCulture) + "KB of UNI <-"); + data1.SetData(DataFormats.UnicodeText, txt); + } + + Logger.Log("Invalid clipboard format received!"); + } + + textTypeCount++; + } + + if (textTypeCount > 0) + { + Clipboard.SetDataObject(data1); + } + } + + private static void SetFileDropList(StringCollection filePaths) + { + Common.DoSomethingInUIThread(() => + { + try + { + _ = Common.Retry( + nameof(SystemClipboard.SetFileDropList), + () => + { + SystemClipboard.SetFileDropList(filePaths); + return true; + }, + (log) => Logger.TelemetryLogTrace( + log, + SeverityLevel.Information), + () => Clipboard.LastClipboardEventTime = Common.GetTick()); + } + catch (ExternalException e) + { + Logger.Log(e); + } + catch (ThreadStateException e) + { + Logger.Log(e); + } + catch (ArgumentNullException e) + { + Logger.Log(e); + } + catch (ArgumentException e) + { + Logger.Log(e); + } + }); + } + + private static void SetImage(Image image) + { + Common.DoSomethingInUIThread(() => + { + try + { + _ = Common.Retry( + nameof(SystemClipboard.SetImage), + () => + { + SystemClipboard.SetImage(image); + return true; + }, + (log) => Logger.TelemetryLogTrace(log, SeverityLevel.Information), + () => Clipboard.LastClipboardEventTime = Common.GetTick()); + } + catch (ExternalException e) + { + Logger.Log(e); + } + catch (ThreadStateException e) + { + Logger.Log(e); + } + catch (ArgumentNullException e) + { + Logger.Log(e); + } + }); + } + + internal static void SetText(string text) + { + Common.DoSomethingInUIThread(() => + { + try + { + _ = Common.Retry( + nameof(SystemClipboard.SetText), + () => + { + SystemClipboard.SetText(text); + return true; + }, + (log) => Logger.TelemetryLogTrace(log, SeverityLevel.Information), + () => Clipboard.LastClipboardEventTime = Common.GetTick()); + } + catch (ExternalException e) + { + Logger.Log(e); + } + catch (ThreadStateException e) + { + Logger.Log(e); + } + catch (ArgumentNullException e) + { + Logger.Log(e); + } + }); + } + + private static void SetDataObject(DataObject dataObject) + { + Common.DoSomethingInUIThread(() => + { + try + { + SystemClipboard.SetDataObject(dataObject, true, 10, 200); + } + catch (ExternalException e) + { + string dataFormats = string.Join(",", dataObject.GetFormats()); + Logger.Log($"{e.Message}: {dataFormats}"); + } + catch (ThreadStateException e) + { + Logger.Log(e); + } + catch (ArgumentNullException e) + { + Logger.Log(e); + } + }); + } +} diff --git a/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs b/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs index 6d13672d3a..2decb83261 100644 --- a/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs +++ b/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs @@ -83,7 +83,7 @@ internal static class DragDrop if (wParam == Common.WM_RBUTTONUP && IsDropping) { IsDropping = false; - Common.LastIDWithClipboardData = ID.NONE; + Clipboard.LastIDWithClipboardData = ID.NONE; } } @@ -193,7 +193,7 @@ internal static class DragDrop { if (!string.IsNullOrEmpty(dragFileName) && (File.Exists(dragFileName) || Directory.Exists(dragFileName))) { - Common.LastDragDropFile = dragFileName; + Clipboard.LastDragDropFile = dragFileName; /* * possibleDropMachineID is used as desID sent in DragDropStep06(); * */ @@ -270,7 +270,7 @@ internal static class DragDrop else { IsDragging = false; - Common.LastIDWithClipboardData = ID.NONE; + Clipboard.LastIDWithClipboardData = ID.NONE; } } } @@ -280,7 +280,7 @@ internal static class DragDrop Logger.LogDebug("DragDropStep10: Hide the form and get data..."); IsDropping = false; IsDragging = false; - Common.LastIDWithClipboardData = ID.NONE; + Clipboard.LastIDWithClipboardData = ID.NONE; Common.DoSomethingInUIThread(() => { @@ -288,7 +288,7 @@ internal static class DragDrop }); PowerToysTelemetry.Log.WriteEvent(new MouseWithoutBorders.Telemetry.MouseWithoutBordersDragAndDropEvent()); - Common.GetRemoteClipboard("desktop"); + Clipboard.GetRemoteClipboard("desktop"); } internal static void DragDropStep11() @@ -298,8 +298,8 @@ internal static class DragDrop IsDropping = false; IsDragging = false; DragMachine = (ID)1; - Common.LastIDWithClipboardData = ID.NONE; - Common.LastDragDropFile = null; + Clipboard.LastIDWithClipboardData = ID.NONE; + Clipboard.LastDragDropFile = null; MouseDown = false; } @@ -307,7 +307,7 @@ internal static class DragDrop { Logger.LogDebug("DragDropStep12: ClipboardDragDropEnd received"); IsDropping = false; - Common.LastIDWithClipboardData = ID.NONE; + Clipboard.LastIDWithClipboardData = ID.NONE; Common.DoSomethingInUIThread(() => { diff --git a/src/modules/MouseWithoutBorders/App/Core/Event.cs b/src/modules/MouseWithoutBorders/App/Core/Event.cs index c30c59e547..1e6ee3e371 100644 --- a/src/modules/MouseWithoutBorders/App/Core/Event.cs +++ b/src/modules/MouseWithoutBorders/App/Core/Event.cs @@ -78,7 +78,7 @@ internal static class Event // if they are, check that there is no application running in fullscreen mode before switching. if (!p.IsEmpty && Common.IsEasyMouseSwitchAllowed()) { - Common.HasSwitchedMachineSinceLastCopy = true; + Clipboard.HasSwitchedMachineSinceLastCopy = true; Logger.LogDebug(string.Format( CultureInfo.CurrentCulture, @@ -218,10 +218,10 @@ internal static class Event if (MachineStuff.desMachineID == Common.MachineID) { - if (Common.GetTick() - Common.clipboardCopiedTime < Common.BIG_CLIPBOARD_DATA_TIMEOUT) + if (Common.GetTick() - Clipboard.clipboardCopiedTime < Clipboard.BIG_CLIPBOARD_DATA_TIMEOUT) { - Common.clipboardCopiedTime = 0; - Common.GetRemoteClipboard("PrepareToSwitchToMachine"); + Clipboard.clipboardCopiedTime = 0; + Clipboard.GetRemoteClipboard("PrepareToSwitchToMachine"); } } else diff --git a/src/modules/MouseWithoutBorders/App/Core/Helper.cs b/src/modules/MouseWithoutBorders/App/Core/Helper.cs index 4122fbe31b..bd66c9a83f 100644 --- a/src/modules/MouseWithoutBorders/App/Core/Helper.cs +++ b/src/modules/MouseWithoutBorders/App/Core/Helper.cs @@ -119,7 +119,7 @@ internal static class Helper if (MachineStuff.NewDesMachineID == Common.MachineID) { - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); } } } @@ -317,7 +317,7 @@ internal static class Helper Common.GetInputDesktop(), 0); - Common.HasSwitchedMachineSinceLastCopy = true; + Clipboard.HasSwitchedMachineSinceLastCopy = true; // Common.CreateLowIntegrityProcess("\"" + Path.GetDirectoryName(Application.ExecutablePath) + "\\MouseWithoutBordersHelper.exe\"", string.Empty, 0, false, 0); var processes = Process.GetProcessesByName(HelperProcessName); diff --git a/src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs b/src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs new file mode 100644 index 0000000000..963775cbca --- /dev/null +++ b/src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs @@ -0,0 +1,278 @@ +// 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.Globalization; +using System.Net.NetworkInformation; +using System.Security.Cryptography; +using System.Threading; + +using Microsoft.Win32; +using MouseWithoutBorders.Class; +using Windows.UI.Input.Preview.Injection; + +// +// Initialization and clean up. +// +// +// 2008 created by Truong Do (ductdo). +// 2009-... modified by Truong Do (TruongDo). +// 2023- Included in PowerToys. +// +namespace MouseWithoutBorders.Core; + +internal static class InitAndCleanup +{ + private static bool initDone; + internal static int REOPEN_WHEN_WSAECONNRESET = -10054; + internal static int REOPEN_WHEN_HOTKEY = -10055; + internal static int PleaseReopenSocket; + internal static bool ReopenSocketDueToReadError; + + private static DateTime LastResumeSuspendTime { get; set; } = DateTime.UtcNow; + + internal static bool InitDone + { + get => InitAndCleanup.initDone; + set => InitAndCleanup.initDone = value; + } + + internal static void UpdateMachineTimeAndID() + { + Common.MachineName = Common.MachineName.Trim(); + _ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true); + } + + private static void InitializeMachinePoolFromSettings() + { + try + { + MachineInf[] info = MachinePoolHelpers.LoadMachineInfoFromMachinePoolStringSetting(Setting.Values.MachinePoolString); + for (int i = 0; i < info.Length; i++) + { + info[i].Name = info[i].Name.Trim(); + } + + MachineStuff.MachinePool.Initialize(info); + MachineStuff.MachinePool.ResetIPAddressesForDeadMachines(true); + } + catch (Exception ex) + { + Logger.Log(ex); + MachineStuff.MachinePool.Clear(); + } + } + + private static void SetupMachineNameAndID() + { + try + { + Common.GetMachineName(); + Common.DesMachineID = MachineStuff.NewDesMachineID = Common.MachineID; + + // MessageBox.Show(machineID.ToString(CultureInfo.CurrentCulture)); // For test + InitializeMachinePoolFromSettings(); + + Common.MachineName = Common.MachineName.Trim(); + _ = MachineStuff.MachinePool.LearnMachine(Common.MachineName); + _ = MachineStuff.MachinePool.TryUpdateMachineID(Common.MachineName, Common.MachineID, true); + + MachineStuff.UpdateMachinePoolStringSetting(); + } + catch (Exception e) + { + Logger.Log(e); + } + } + + internal static void Init() + { + _ = Helper.GetUserName(); + Common.GeneratedKey = true; + + try + { + Common.MyKey = Setting.Values.MyKey; + int tmp = Setting.Values.MyKeyDaysToExpire; + } + catch (FormatException e) + { + Common.KeyCorrupted = true; + Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); + Logger.Log(e.Message); + } + catch (CryptographicException e) + { + Common.KeyCorrupted = true; + Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey(); + Logger.Log(e.Message); + } + + try + { + InputSimulation.Injector = InputInjector.TryCreate(); + if (InputSimulation.Injector != null) + { + InputSimulation.MoveMouseRelative(0, 0); + NativeMethods.InjectMouseInputAvailable = true; + } + } + catch (EntryPointNotFoundException) + { + NativeMethods.InjectMouseInputAvailable = false; + Logger.Log($"{nameof(NativeMethods.InjectMouseInputAvailable)} = false"); + } + + bool dummy = Setting.Values.DrawMouseEx; + Common.Is64bitOS = IntPtr.Size == 8; + Common.tcpPort = Setting.Values.TcpPort; + Common.GetScreenConfig(); + Common.PackageSent = new PackageMonitor(0); + Common.PackageReceived = new PackageMonitor(0); + SetupMachineNameAndID(); + Common.InitEncryption(); + CreateHelperThreads(); + + SystemEvents.DisplaySettingsChanged += new EventHandler(Common.SystemEvents_DisplaySettingsChanged); + NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged); + SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged); + PleaseReopenSocket = 9; + /* TODO: Telemetry for the matrix? */ + } + + private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) + { + Helper.WndProcCounter++; + + if (e.Mode is PowerModes.Resume or PowerModes.Suspend) + { + Logger.TelemetryLogTrace($"{nameof(SystemEvents_PowerModeChanged)}: {e.Mode}", SeverityLevel.Information); + LastResumeSuspendTime = DateTime.UtcNow; + MachineStuff.SwitchToMultipleMode(false, true); + } + } + + private static void CreateHelperThreads() + { + // NOTE(@yuyoyuppe): service crashes while trying to obtain this info, disabling. + /* + Thread watchDogThread = new(new ThreadStart(WatchDogThread), nameof(WatchDogThread)); + watchDogThread.Priority = ThreadPriority.Highest; + watchDogThread.Start(); + */ + + Common.helper = new Thread(new ThreadStart(Helper.HelperThread), "Helper Thread"); + Common.helper.SetApartmentState(ApartmentState.STA); + Common.helper.Start(); + } + + private static void AskHelperThreadsToExit(int waitTime) + { + Helper.signalHelperToExit = true; + Helper.signalWatchDogToExit = true; + _ = Common.EvSwitch.Set(); + + int c = 0; + if (Common.helper != null && c < waitTime) + { + while (Helper.signalHelperToExit) + { + Thread.Sleep(1); + } + + Common.helper = null; + } + } + + internal static void Cleanup() + { + try + { + Common.SendByeBye(); + + // UnhookClipboard(); + AskHelperThreadsToExit(500); + Common.MainForm.NotifyIcon.Visible = false; + Common.MainForm.NotifyIcon.Dispose(); + Common.CloseAllFormsAndHooks(); + + Common.DoSomethingInUIThread(() => + { + Common.Sk?.Close(true); + }); + } + catch (Exception e) + { + Logger.Log(e); + } + } + + private static long lastReleaseAllKeysCall; + + internal static void ReleaseAllKeys() + { + if (Math.Abs(Common.GetTick() - lastReleaseAllKeysCall) < 2000) + { + return; + } + + lastReleaseAllKeysCall = Common.GetTick(); + + KEYBDDATA kd; + kd.dwFlags = (int)Common.LLKHF.UP; + + VK[] keys = new VK[] + { + VK.LSHIFT, VK.LCONTROL, VK.LMENU, VK.LWIN, VK.RSHIFT, + VK.RCONTROL, VK.RMENU, VK.RWIN, VK.SHIFT, VK.MENU, VK.CONTROL, + }; + + Logger.LogDebug("***** ReleaseAllKeys has been called! *****:"); + + foreach (VK vk in keys) + { + if ((NativeMethods.GetAsyncKeyState((IntPtr)vk) & 0x8000) != 0) + { + Logger.LogDebug(vk.ToString() + " is down, release it..."); + Common.Hook?.ResetLastSwitchKeys(); // Sticky key can turn ALL PC mode on (CtrlCtrlCtrl) + kd.wVk = (int)vk; + InputSimulation.SendKey(kd); + Common.Hook?.ResetLastSwitchKeys(); + } + } + } + + private static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e) + { + Logger.LogDebug("NetworkAvailabilityEventArgs.IsAvailable: " + e.IsAvailable.ToString(CultureInfo.InvariantCulture)); + Helper.WndProcCounter++; + ScheduleReopenSocketsDueToNetworkChanges(!e.IsAvailable); + } + + private static void ScheduleReopenSocketsDueToNetworkChanges(bool closeSockets = true) + { + if (closeSockets) + { + // Slept/hibernated machine may still have the sockets' status as Connected:( (unchanged) so it would not re-connect after a timeout when waking up. + // Closing the sockets when it is going to sleep/hibernate will trigger the reconnection faster when it wakes up. + Common.DoSomethingInUIThread( + () => + { + SocketStuff s = Common.Sk; + Common.Sk = null; + s?.Close(false); + }, + true); + } + + if (!Common.IsMyDesktopActive()) + { + PleaseReopenSocket = 0; + } + else if (PleaseReopenSocket != 10) + { + PleaseReopenSocket = 10; + } + } +} diff --git a/src/modules/MouseWithoutBorders/App/Core/Logger.cs b/src/modules/MouseWithoutBorders/App/Core/Logger.cs index 6635de59f0..86ce7605b5 100644 --- a/src/modules/MouseWithoutBorders/App/Core/Logger.cs +++ b/src/modules/MouseWithoutBorders/App/Core/Logger.cs @@ -247,22 +247,24 @@ internal static class Logger internal static void DumpStaticTypes(StringBuilder sb, int level) { - sb.AppendLine($"[{nameof(DragDrop)}]\r\n==============="); - Logger.DumpType(sb, typeof(DragDrop), 0, level); - sb.AppendLine($"[{nameof(Event)}]\r\n==============="); - Logger.DumpType(sb, typeof(Event), 0, level); - sb.AppendLine($"[{nameof(Helper)}]\r\n==============="); - Logger.DumpType(sb, typeof(Helper), 0, level); - sb.AppendLine($"[{nameof(Launch)}]\r\n==============="); - Logger.DumpType(sb, typeof(Launch), 0, level); - sb.AppendLine($"[{nameof(Logger)}]\r\n==============="); - Logger.DumpType(sb, typeof(Logger), 0, level); - sb.AppendLine($"[{nameof(MachineStuff)}]\r\n==============="); - Logger.DumpType(sb, typeof(MachineStuff), 0, level); - sb.AppendLine($"[{nameof(Receiver)}]\r\n==============="); - Logger.DumpType(sb, typeof(Receiver), 0, level); - sb.AppendLine($"[{nameof(Service)}]\r\n==============="); - Logger.DumpType(sb, typeof(Service), 0, level); + var staticTypes = new List + { + typeof(Clipboard), + typeof(DragDrop), + typeof(Event), + typeof(InitAndCleanup), + typeof(Helper), + typeof(Launch), + typeof(Logger), + typeof(MachineStuff), + typeof(Receiver), + typeof(Service), + }; + foreach (var staticType in staticTypes) + { + sb.AppendLine(CultureInfo.InvariantCulture, $"[{staticType.Name}]\r\n==============="); + Logger.DumpType(sb, staticType, 0, level); + } } internal static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool stop) diff --git a/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs b/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs index 144102629f..e5263aa788 100644 --- a/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs +++ b/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs @@ -992,7 +992,7 @@ internal static class MachineStuff Setting.Values.MatrixOneRow = !((package.Type & PackageType.MatrixTwoRowFlag) == PackageType.MatrixTwoRowFlag); MachineMatrix = MachineMatrix; // Save - Common.ReopenSocketDueToReadError = true; + InitAndCleanup.ReopenSocketDueToReadError = true; UpdateClientSockets("UpdateMachineMatrix"); @@ -1044,7 +1044,7 @@ internal static class MachineStuff Common.MoveMouseToCenter(); } - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); Common.UpdateMultipleModeIconAndMenu(); } diff --git a/src/modules/MouseWithoutBorders/App/Core/Receiver.cs b/src/modules/MouseWithoutBorders/App/Core/Receiver.cs index 303bec4ff0..1b1e0730b0 100644 --- a/src/modules/MouseWithoutBorders/App/Core/Receiver.cs +++ b/src/modules/MouseWithoutBorders/App/Core/Receiver.cs @@ -157,7 +157,7 @@ internal static class Receiver if (!p.IsEmpty) { - Common.HasSwitchedMachineSinceLastCopy = true; + Clipboard.HasSwitchedMachineSinceLastCopy = true; Logger.LogDebug(string.Format( CultureInfo.CurrentCulture, @@ -274,7 +274,7 @@ internal static class Receiver Common.PackageReceived.Clipboard++; if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop) { - Common.clipboardCopiedTime = Common.GetTick(); + Clipboard.clipboardCopiedTime = Common.GetTick(); GetNameOfMachineWithClipboardData(package); SignalBigClipboardData(); } @@ -282,10 +282,10 @@ internal static class Receiver break; case PackageType.MachineSwitched: - if (Common.GetTick() - Common.clipboardCopiedTime < Common.BIG_CLIPBOARD_DATA_TIMEOUT && (package.Des == Common.MachineID)) + if (Common.GetTick() - Clipboard.clipboardCopiedTime < Clipboard.BIG_CLIPBOARD_DATA_TIMEOUT && (package.Des == Common.MachineID)) { - Common.clipboardCopiedTime = 0; - Common.GetRemoteClipboard("PackageType.MachineSwitched"); + Clipboard.clipboardCopiedTime = 0; + Clipboard.GetRemoteClipboard("PackageType.MachineSwitched"); } break; @@ -297,7 +297,7 @@ internal static class Receiver if (package.Des == Common.MachineID || package.Des == ID.ALL) { GetNameOfMachineWithClipboardData(package); - Common.GetRemoteClipboard("mspaint," + Common.LastMachineWithClipboardData); + Clipboard.GetRemoteClipboard("mspaint," + Clipboard.LastMachineWithClipboardData); } } @@ -326,10 +326,10 @@ internal static class Receiver Thread.UpdateThreads(thread); string remoteMachine = package.MachineName; - System.Net.Sockets.TcpClient client = Common.ConnectToRemoteClipboardSocket(remoteMachine); + System.Net.Sockets.TcpClient client = Clipboard.ConnectToRemoteClipboardSocket(remoteMachine); bool clientPushData = true; - if (Common.ShakeHand(ref remoteMachine, client.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref package.PostAction)) + if (Clipboard.ShakeHand(ref remoteMachine, client.Client, out Stream enStream, out Stream deStream, ref clientPushData, ref package.PostAction)) { SocketStuff.SendClipboardData(client.Client, enStream); } @@ -360,7 +360,7 @@ internal static class Receiver case PackageType.ClipboardText: case PackageType.ClipboardImage: - Common.clipboardCopiedTime = 0; + Clipboard.clipboardCopiedTime = 0; if (package.Type == PackageType.ClipboardImage) { Common.PackageReceived.ClipboardImage++; @@ -372,7 +372,7 @@ internal static class Receiver if (tcp != null) { - Common.ReceiveClipboardDataUsingTCP( + Clipboard.ReceiveClipboardDataUsingTCP( package, package.Type == PackageType.ClipboardImage, tcp); @@ -381,10 +381,10 @@ internal static class Receiver break; case PackageType.HideMouse: - Common.HasSwitchedMachineSinceLastCopy = true; + Clipboard.HasSwitchedMachineSinceLastCopy = true; Common.HideMouseCursor(true); Helper.MainFormDotEx(false); - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); break; default: @@ -405,11 +405,11 @@ internal static class Receiver internal static void GetNameOfMachineWithClipboardData(DATA package) { - Common.LastIDWithClipboardData = package.Src; - List matchingMachines = MachineStuff.MachinePool.TryFindMachineByID(Common.LastIDWithClipboardData); + Clipboard.LastIDWithClipboardData = package.Src; + List matchingMachines = MachineStuff.MachinePool.TryFindMachineByID(Clipboard.LastIDWithClipboardData); if (matchingMachines.Count >= 1) { - Common.LastMachineWithClipboardData = matchingMachines[0].Name.Trim(); + Clipboard.LastMachineWithClipboardData = matchingMachines[0].Name.Trim(); } /* diff --git a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage3a.cs b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage3a.cs index 84d464d33d..97884d4821 100644 --- a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage3a.cs +++ b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage3a.cs @@ -84,7 +84,7 @@ namespace MouseWithoutBorders if ((connectedClientSocket = Common.GetConnectedClientSocket()) != null) { ShowStatus($"Connected from local IP Address: {connectedClientSocket.Address}."); - Common.UpdateMachineTimeAndID(); + InitAndCleanup.UpdateMachineTimeAndID(); Common.MMSleep(1); connected = true; diff --git a/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs b/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs index ca095d8140..66301c52cb 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs @@ -22,6 +22,8 @@ using Microsoft.PowerToys.Telemetry; // using MouseWithoutBorders.Class; using MouseWithoutBorders.Core; + +using Clipboard = MouseWithoutBorders.Core.Clipboard; using Timer = System.Windows.Forms.Timer; [module: SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Scope = "member", Target = "MouseWithoutBorders.frmMatrix.#buttonOK_Click(System.Object,System.EventArgs)", Justification = "Dotnet port with style preservation")] @@ -110,7 +112,7 @@ namespace MouseWithoutBorders { SocketStuff.InvalidKeyFound = false; showInvalidKeyMessage = false; - Common.ReopenSocketDueToReadError = true; + InitAndCleanup.ReopenSocketDueToReadError = true; Common.ReopenSockets(true); for (int i = 0; i < 10; i++) @@ -780,7 +782,7 @@ namespace MouseWithoutBorders ShowUpdateMessage(); - Common.HasSwitchedMachineSinceLastCopy = true; + Clipboard.HasSwitchedMachineSinceLastCopy = true; } private void CheckBoxDisableCAD_CheckedChanged(object sender, EventArgs e) diff --git a/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs b/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs index b01a653f60..1ab0ce8cc7 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs @@ -139,13 +139,13 @@ namespace MouseWithoutBorders { if (cleanup) { - Common.Cleanup(); + InitAndCleanup.Cleanup(); } Helper.WndProcCounter++; if (!Common.RunOnScrSaverDesktop) { - Common.ReleaseAllKeys(); + InitAndCleanup.ReleaseAllKeys(); } Helper.RunDDHelper(true); @@ -412,7 +412,7 @@ namespace MouseWithoutBorders count = 0; - Common.InitDone = true; + InitAndCleanup.InitDone = true; #if SHOW_ON_WINLOGON if (Common.RunOnLogonDesktop) { @@ -423,39 +423,39 @@ namespace MouseWithoutBorders if ((count % 2) == 0) { - if (Common.PleaseReopenSocket == 10 || (Common.PleaseReopenSocket > 0 && count > 0 && count % 300 == 0)) + if (InitAndCleanup.PleaseReopenSocket == 10 || (InitAndCleanup.PleaseReopenSocket > 0 && count > 0 && count % 300 == 0)) { - if (!Common.AtLeastOneSocketEstablished() || Common.PleaseReopenSocket == 10) + if (!Common.AtLeastOneSocketEstablished() || InitAndCleanup.PleaseReopenSocket == 10) { Thread.Sleep(1000); - if (Common.PleaseReopenSocket > 0) + if (InitAndCleanup.PleaseReopenSocket > 0) { - Common.PleaseReopenSocket--; + InitAndCleanup.PleaseReopenSocket--; } // Double check. if (!Common.AtLeastOneSocketEstablished()) { Common.GetMachineName(); - Logger.LogDebug("Common.pleaseReopenSocket: " + Common.PleaseReopenSocket.ToString(CultureInfo.InvariantCulture)); + Logger.LogDebug("Common.pleaseReopenSocket: " + InitAndCleanup.PleaseReopenSocket.ToString(CultureInfo.InvariantCulture)); Common.ReopenSockets(false); MachineStuff.NewDesMachineID = Common.DesMachineID = Common.MachineID; } } else { - Common.PleaseReopenSocket = 0; + InitAndCleanup.PleaseReopenSocket = 0; } } - if (Common.PleaseReopenSocket == Common.REOPEN_WHEN_HOTKEY) + if (InitAndCleanup.PleaseReopenSocket == InitAndCleanup.REOPEN_WHEN_HOTKEY) { - Common.PleaseReopenSocket = 0; + InitAndCleanup.PleaseReopenSocket = 0; Common.ReopenSockets(true); } - else if (Common.PleaseReopenSocket == Common.REOPEN_WHEN_WSAECONNRESET) + else if (InitAndCleanup.PleaseReopenSocket == InitAndCleanup.REOPEN_WHEN_WSAECONNRESET) { - Common.PleaseReopenSocket = 0; + InitAndCleanup.PleaseReopenSocket = 0; Thread.Sleep(1000); MachineStuff.UpdateClientSockets("REOPEN_WHEN_WSAECONNRESET"); } 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 3c933bfff1..1bbd8ba49c 100644 --- a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt +++ b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt @@ -4,28 +4,6 @@ [Other Logs] =============== = MouseWithoutBorders.Common -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 screenWidth = 0 screenHeight = 0 lastX = 0 @@ -99,17 +77,6 @@ LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte --_budget = ???????????? --_growLockArray = True --_comparerIsDefaultForClasses = False -initDone = False -REOPEN_WHEN_WSAECONNRESET = -10054 -REOPEN_WHEN_HOTKEY = -10055 -PleaseReopenSocket = 0 -ReopenSocketDueToReadError = False -k__BackingField = ???????????? ---_dateData = ???????????? ---MinValue = 01/01/0001 00:00:00 ---MaxValue = 31/12/9999 23:59:59 ---UnixEpoch = 01/01/1970 00:00:00 -lastReleaseAllKeysCall = 0 PackageSent = MouseWithoutBorders.PackageMonitor --Keyboard = 0 --Mouse = 0 @@ -153,12 +120,6 @@ p = {X=0,Y=0} --y = 0 --Empty = {X=0,Y=0} k__BackingField = False -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} TOGGLE_ICONS_SIZE = 4 ICON_ONE = 0 ICON_ALL = 1 @@ -195,6 +156,36 @@ WM_KEYDOWN = 256 WM_KEYUP = 257 WM_SYSKEYDOWN = 260 WM_SYSKEYUP = 261 +[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 @@ -249,6 +240,19 @@ actualLastPos = {X=0,Y=0} --Empty = {X=0,Y=0} myLastX = 0 myLastY = 0 +[InitAndCleanup] +=============== +initDone = False +REOPEN_WHEN_WSAECONNRESET = -10054 +REOPEN_WHEN_HOTKEY = -10055 +PleaseReopenSocket = 0 +ReopenSocketDueToReadError = False +k__BackingField = ???????????? +--_dateData = ???????????? +--MinValue = 01/01/0001 00:00:00 +--MaxValue = 31/12/9999 23:59:59 +--UnixEpoch = 01/01/1970 00:00:00 +lastReleaseAllKeysCall = 0 [Helper] =============== signalHelperToExit = False