diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..7fe8baf --- /dev/null +++ b/Program.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Threading; + +namespace WindowEvader +{ + internal static class Program + { + private const int ProximityThreshold = 70; + private const int BaseMoveDistance = 60; + private const int UpdateIntervalMilliseconds = 15; + + private static Rectangle _desktopBounds; + private static readonly uint CurrentProcessId = (uint)Process.GetCurrentProcess().Id; + + private static void Main() + { + IntPtr consoleWindowHandle = NativeMethods.GetConsoleWindow(); + NativeMethods.ShowWindow(consoleWindowHandle, 0); // Hide console window + + _desktopBounds = GetAllMonitorBounds(); + Point lastCursorPosition = GetCursorPosition(); + + while (true) + { + Point currentCursorPosition = GetCursorPosition(); + double cursorSpeed = CalculateDistance(currentCursorPosition, lastCursorPosition) / (UpdateIntervalMilliseconds / 1000.0); + + foreach (IntPtr windowHandle in GetAllWindows()) + { + ProcessWindow(windowHandle, currentCursorPosition, cursorSpeed); + } + + lastCursorPosition = currentCursorPosition; + Thread.Sleep(UpdateIntervalMilliseconds); + } + } + + private static void ProcessWindow(IntPtr windowHandle, Point cursorPosition, double cursorSpeed) + { + if (!IsEligibleWindow(windowHandle)) + { + return; + } + + if (!NativeMethods.GetWindowRect(windowHandle, out NativeMethods.RECT r)) + { + return; + } + + var windowRect = new Rectangle(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); + var proximityZone = Rectangle.Inflate(windowRect, ProximityThreshold, ProximityThreshold); + + if (!proximityZone.Contains(cursorPosition)) + { + return; + } + + MoveWindowAwayFromCursor(windowHandle, windowRect, cursorPosition, cursorSpeed); + } + + private static bool IsEligibleWindow(IntPtr windowHandle) + { + if (!NativeMethods.IsWindowVisible(windowHandle) || NativeMethods.GetWindowTextLength(windowHandle) == 0) + { + return false; + } + + NativeMethods.GetWindowThreadProcessId(windowHandle, out uint processId); + if (processId == CurrentProcessId) + { + return false; + } + + return !IsMaximized(windowHandle); + } + + private static void MoveWindowAwayFromCursor(IntPtr windowHandle, Rectangle windowRect, Point cursorPosition, double cursorSpeed) + { + var windowCenter = new Point(windowRect.Left + windowRect.Width / 2, windowRect.Top + windowRect.Height / 2); + double distanceToCursor = CalculateDistance(cursorPosition, windowCenter); + + int moveDistance = Math.Max(BaseMoveDistance, (int)(cursorSpeed * 0.05 + (ProximityThreshold - distanceToCursor))); + + int dx = windowCenter.X - cursorPosition.X; + int dy = windowCenter.Y - cursorPosition.Y; + double vectorLength = Math.Sqrt(dx * dx + dy * dy); + if (vectorLength == 0) vectorLength = 1; + + int newX = windowRect.Left + (int)(moveDistance * dx / vectorLength); + int newY = windowRect.Top + (int)(moveDistance * dy / vectorLength); + + // Teleport window to the opposite side if it hits a screen boundary + if (newX < _desktopBounds.Left) + newX = _desktopBounds.Right - windowRect.Width; + else if (newX + windowRect.Width > _desktopBounds.Right) + newX = _desktopBounds.Left; + + if (newY < _desktopBounds.Top) + newY = _desktopBounds.Bottom - windowRect.Height; + else if (newY + windowRect.Height > _desktopBounds.Bottom) + newY = _desktopBounds.Top; + + NativeMethods.MoveWindow(windowHandle, newX, newY, windowRect.Width, windowRect.Height, true); + } + + private static IEnumerable GetAllWindows() + { + var windows = new List(); + NativeMethods.EnumWindows((hWnd, _) => { windows.Add(hWnd); return true; }, IntPtr.Zero); + return windows; + } + + private static double CalculateDistance(Point a, Point b) + { + int dx = a.X - b.X; + int dy = a.Y - b.Y; + return Math.Sqrt(dx * dx + dy * dy); + } + + private static bool IsMaximized(IntPtr windowHandle) + { + var placement = new NativeMethods.WINDOWPLACEMENT(); + placement.length = Marshal.SizeOf(placement); + return NativeMethods.GetWindowPlacement(windowHandle, ref placement) && placement.showCmd == 3; // SW_MAXIMIZE + } + + private static Point GetCursorPosition() + { + NativeMethods.GetCursorPos(out NativeMethods.POINT p); + return new Point(p.X, p.Y); + } + + private static Rectangle GetAllMonitorBounds() + { + var bounds = Rectangle.Empty; + NativeMethods.MonitorEnumProc callback = (IntPtr hMon, IntPtr hdc, ref NativeMethods.RECT rect, IntPtr d) => + { + var mi = new NativeMethods.MONITORINFOEX(); + mi.cbSize = Marshal.SizeOf(mi); + if (NativeMethods.GetMonitorInfo(hMon, ref mi)) + { + var monitorRect = new Rectangle(mi.rcWork.Left, mi.rcWork.Top, + mi.rcWork.Right - mi.rcWork.Left, mi.rcWork.Bottom - mi.rcWork.Top); + bounds = Rectangle.Union(bounds, monitorRect); + } + return true; + }; + NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, IntPtr.Zero); + return bounds; + } + } + + internal static partial class NativeMethods + { + internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + internal delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData); + + [DllImport("kernel32.dll")] + internal static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll")] + internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll")] + internal static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll")] + internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [DllImport("user32.dll")] + internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); + + [DllImport("user32.dll")] + internal static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + internal static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll")] + internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + [DllImport("user32.dll")] + internal static extern bool GetCursorPos(out POINT lpPoint); + + [DllImport("user32.dll")] + internal static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + internal static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); + + [DllImport("user32.dll")] + internal static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); + + [StructLayout(LayoutKind.Sequential)] + internal struct RECT { public int Left, Top, Right, Bottom; } + + [StructLayout(LayoutKind.Sequential)] + internal struct POINT { public int X, Y; } + + [StructLayout(LayoutKind.Sequential)] + internal struct WINDOWPLACEMENT + { + public int length; + public int flags; + public int showCmd; + public POINT ptMinPosition; + public POINT ptMaxPosition; + public RECT rcNormalPosition; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct MONITORINFOEX + { + public int cbSize; + public RECT rcMonitor; + public RECT rcWork; + public uint dwFlags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string szDevice; + } + } +} \ No newline at end of file diff --git a/Scripts/kill_window_evader.bat b/Scripts/kill_window_evader.bat new file mode 100644 index 0000000..b206cea --- /dev/null +++ b/Scripts/kill_window_evader.bat @@ -0,0 +1,10 @@ +@echo off +taskkill /F /IM WindowEvader.exe >nul 2>&1 + +if %ERRORLEVEL%==0 ( + echo WindowEvader was terminated successfully. +) else ( + echo Failed to terminate WindowEvader. It may not be running. +) + +pause diff --git a/WindowEvader.csproj b/WindowEvader.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/WindowEvader.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/WindowEvader.sln b/WindowEvader.sln new file mode 100644 index 0000000..5fb71e2 --- /dev/null +++ b/WindowEvader.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36401.2 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowEvader", "WindowEvader.csproj", "{37F59A95-C40B-47F9-A43C-5857327565B1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {37F59A95-C40B-47F9-A43C-5857327565B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37F59A95-C40B-47F9-A43C-5857327565B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37F59A95-C40B-47F9-A43C-5857327565B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37F59A95-C40B-47F9-A43C-5857327565B1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0583E8A6-A6CA-4E42-8572-A90D048F307D} + EndGlobalSection +EndGlobal