WinAPI.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Diagnostics; 8 using System.Drawing; 9 using System.Globalization; 10 using System.Linq; 11 using System.Runtime.InteropServices; 12 using System.Threading; 13 using System.Windows.Forms; 14 15 using MouseWithoutBorders.Class; 16 17 // <summary> 18 // Screen/Desktop helper functions. 19 // </summary> 20 // <history> 21 // 2008 created by Truong Do (ductdo). 22 // 2009-... modified by Truong Do (TruongDo). 23 // 2023- Included in PowerToys. 24 // </history> 25 namespace MouseWithoutBorders.Core; 26 27 // Desktops, and GetScreenConfig routines 28 internal static class WinAPI 29 { 30 private static MyRectangle newDesktopBounds; 31 private static MyRectangle newPrimaryScreenBounds; 32 private static string activeDesktop; 33 34 private static string ActiveDesktop => WinAPI.activeDesktop; 35 36 internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) 37 { 38 GetScreenConfig(); 39 } 40 41 internal static readonly List<Point> SensitivePoints = new(); 42 43 private static bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref NativeMethods.RECT lprcMonitor, IntPtr dwData) 44 { 45 // lprcMonitor is wrong!!! => using GetMonitorInfo(...) 46 // Log(String.Format( CultureInfo.CurrentCulture,"MONITOR: l{0}, t{1}, r{2}, b{3}", lprcMonitor.Left, lprcMonitor.Top, lprcMonitor.Right, lprcMonitor.Bottom)); 47 NativeMethods.MonitorInfoEx mi = default; 48 mi.cbSize = Marshal.SizeOf(mi); 49 _ = NativeMethods.GetMonitorInfo(hMonitor, ref mi); 50 51 try 52 { 53 // For logging only 54 _ = NativeMethods.GetDpiForMonitor(hMonitor, 0, out uint dpiX, out uint dpiY); 55 Logger.Log(string.Format(CultureInfo.CurrentCulture, "MONITOR: ({0}, {1}, {2}, {3}). DPI: ({4}, {5})", mi.rcMonitor.Left, mi.rcMonitor.Top, mi.rcMonitor.Right, mi.rcMonitor.Bottom, dpiX, dpiY)); 56 } 57 catch (DllNotFoundException) 58 { 59 Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower."); 60 } 61 catch (EntryPointNotFoundException) 62 { 63 Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower."); 64 } 65 catch (Exception e) 66 { 67 Logger.Log(e); 68 } 69 70 if (mi.rcMonitor.Left == 0 && mi.rcMonitor.Top == 0 && mi.rcMonitor.Right != 0 && mi.rcMonitor.Bottom != 0) 71 { 72 // Primary screen 73 _ = Interlocked.Exchange(ref Common.screenWidth, mi.rcMonitor.Right - mi.rcMonitor.Left); 74 _ = Interlocked.Exchange(ref Common.screenHeight, mi.rcMonitor.Bottom - mi.rcMonitor.Top); 75 76 newPrimaryScreenBounds.Left = mi.rcMonitor.Left; 77 newPrimaryScreenBounds.Top = mi.rcMonitor.Top; 78 newPrimaryScreenBounds.Right = mi.rcMonitor.Right; 79 newPrimaryScreenBounds.Bottom = mi.rcMonitor.Bottom; 80 } 81 else 82 { 83 if (mi.rcMonitor.Left < newDesktopBounds.Left) 84 { 85 newDesktopBounds.Left = mi.rcMonitor.Left; 86 } 87 88 if (mi.rcMonitor.Top < newDesktopBounds.Top) 89 { 90 newDesktopBounds.Top = mi.rcMonitor.Top; 91 } 92 93 if (mi.rcMonitor.Right > newDesktopBounds.Right) 94 { 95 newDesktopBounds.Right = mi.rcMonitor.Right; 96 } 97 98 if (mi.rcMonitor.Bottom > newDesktopBounds.Bottom) 99 { 100 newDesktopBounds.Bottom = mi.rcMonitor.Bottom; 101 } 102 } 103 104 lock (SensitivePoints) 105 { 106 SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Top)); 107 SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Top)); 108 SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Bottom)); 109 SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Bottom)); 110 } 111 112 return true; 113 } 114 115 internal static void GetScreenConfig() 116 { 117 try 118 { 119 Logger.LogDebug("==================== GetScreenConfig started"); 120 newDesktopBounds = new MyRectangle(); 121 newPrimaryScreenBounds = new MyRectangle(); 122 newDesktopBounds.Left = newPrimaryScreenBounds.Left = Screen.PrimaryScreen.Bounds.Left; 123 newDesktopBounds.Top = newPrimaryScreenBounds.Top = Screen.PrimaryScreen.Bounds.Top; 124 newDesktopBounds.Right = newPrimaryScreenBounds.Right = Screen.PrimaryScreen.Bounds.Right; 125 newDesktopBounds.Bottom = newPrimaryScreenBounds.Bottom = Screen.PrimaryScreen.Bounds.Bottom; 126 127 Logger.Log(string.Format( 128 CultureInfo.CurrentCulture, 129 "logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}", 130 Common.RunOnLogonDesktop, 131 WinAPI.newPrimaryScreenBounds.Left, 132 WinAPI.newPrimaryScreenBounds.Top, 133 WinAPI.newPrimaryScreenBounds.Right, 134 WinAPI.newPrimaryScreenBounds.Bottom, 135 WinAPI.newDesktopBounds.Left, 136 WinAPI.newDesktopBounds.Top, 137 WinAPI.newDesktopBounds.Right, 138 WinAPI.newDesktopBounds.Bottom)); 139 140 #if USE_MANAGED_ROUTINES 141 // Managed routines do not work well when running on secure desktop:( 142 screenWidth = Screen.PrimaryScreen.Bounds.Width; 143 screenHeight = Screen.PrimaryScreen.Bounds.Height; 144 screenCount = Screen.AllScreens.Length; 145 for (int i = 0; i < Screen.AllScreens.Length; i++) 146 { 147 if (Screen.AllScreens[i].Bounds.Left < desktopBounds.Left) desktopBounds.Left = Screen.AllScreens[i].Bounds.Left; 148 if (Screen.AllScreens[i].Bounds.Top < desktopBounds.Top) desktopBounds.Top = Screen.AllScreens[i].Bounds.Top; 149 if (Screen.AllScreens[i].Bounds.Right > desktopBounds.Right) desktopBounds.Right = Screen.AllScreens[i].Bounds.Right; 150 if (Screen.AllScreens[i].Bounds.Bottom > desktopBounds.Bottom) desktopBounds.Bottom = Screen.AllScreens[i].Bounds.Bottom; 151 } 152 #else 153 lock (SensitivePoints) 154 { 155 SensitivePoints.Clear(); 156 } 157 158 NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero); 159 160 // 1000 calls to EnumDisplayMonitors cost a dozen of milliseconds 161 #endif 162 Interlocked.Exchange(ref MachineStuff.desktopBounds, newDesktopBounds); 163 Interlocked.Exchange(ref MachineStuff.primaryScreenBounds, newPrimaryScreenBounds); 164 165 Logger.Log(string.Format( 166 CultureInfo.CurrentCulture, 167 "logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}", 168 Common.RunOnLogonDesktop, 169 MachineStuff.PrimaryScreenBounds.Left, 170 MachineStuff.PrimaryScreenBounds.Top, 171 MachineStuff.PrimaryScreenBounds.Right, 172 MachineStuff.PrimaryScreenBounds.Bottom, 173 MachineStuff.DesktopBounds.Left, 174 MachineStuff.DesktopBounds.Top, 175 MachineStuff.DesktopBounds.Right, 176 MachineStuff.DesktopBounds.Bottom)); 177 178 Logger.Log("==================== GetScreenConfig ended"); 179 } 180 catch (Exception e) 181 { 182 Logger.Log(e); 183 } 184 } 185 186 #if USING_SCREEN_SAVER_ROUTINES 187 [DllImport("user32.dll", CharSet = CharSet.Auto)] 188 private static extern int PostMessage(IntPtr hWnd, int wMsg, int wParam, int lParam); 189 190 [DllImport("user32.dll", CharSet = CharSet.Auto)] 191 private static extern IntPtr OpenDesktop(string hDesktop, int Flags, bool Inherit, UInt32 DesiredAccess); 192 193 [DllImport("user32.dll", CharSet = CharSet.Auto)] 194 private static extern bool CloseDesktop(IntPtr hDesktop); 195 196 [DllImport("user32.dll", CharSet = CharSet.Auto)] 197 private static extern bool EnumDesktopWindows( IntPtr hDesktop, EnumDesktopWindowsProc callback, IntPtr lParam); 198 199 [DllImport("user32.dll", CharSet = CharSet.Auto)] 200 private static extern bool IsWindowVisible(IntPtr hWnd); 201 202 [DllImport("user32.dll", CharSet = CharSet.Auto)] 203 private static extern bool SystemParametersInfo(int uAction, int uParam, ref int pvParam, int flags); 204 205 private delegate bool EnumDesktopWindowsProc(IntPtr hDesktop, IntPtr lParam); 206 private const int WM_CLOSE = 16; 207 private const int SPI_GETSCREENSAVERRUNNING = 114; 208 209 internal static bool IsScreenSaverRunning() 210 { 211 int isRunning = 0; 212 SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0,ref isRunning, 0); 213 return (isRunning != 0); 214 } 215 216 internal static void CloseScreenSaver() 217 { 218 IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS); 219 if (hDesktop != IntPtr.Zero) 220 { 221 LogDebug("Closing screen saver..."); 222 EnumDesktopWindows(hDesktop, new EnumDesktopWindowsProc(CloseScreenSaverFunc), IntPtr.Zero); 223 CloseDesktop(hDesktop); 224 } 225 } 226 227 private static bool CloseScreenSaverFunc(IntPtr hWnd, IntPtr lParam) 228 { 229 if (IsWindowVisible(hWnd)) 230 { 231 LogDebug("Posting WM_CLOSE to " + hWnd.ToString(CultureInfo.InvariantCulture)); 232 PostMessage(hWnd, WM_CLOSE, 0, 0); 233 } 234 return true; 235 } 236 #endif 237 238 internal static string GetMyDesktop() 239 { 240 byte[] arThreadDesktop = new byte[256]; 241 IntPtr hD = NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId()); 242 if (hD != IntPtr.Zero) 243 { 244 _ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arThreadDesktop, arThreadDesktop.Length, out _); 245 return Common.GetString(arThreadDesktop).Replace("\0", string.Empty); 246 } 247 248 return string.Empty; 249 } 250 251 internal static string GetInputDesktop() 252 { 253 byte[] arInputDesktop = new byte[256]; 254 IntPtr hD = NativeMethods.OpenInputDesktop(0, false, NativeMethods.DESKTOP_READOBJECTS); 255 if (hD != IntPtr.Zero) 256 { 257 _ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arInputDesktop, arInputDesktop.Length, out _); 258 return Common.GetString(arInputDesktop).Replace("\0", string.Empty); 259 } 260 261 return string.Empty; 262 } 263 264 private static void StartMMService(string desktopToRunMouseWithoutBordersOn) 265 { 266 if (!Common.RunWithNoAdminRight) 267 { 268 Logger.LogDebug("*** Starting on active Desktop: " + desktopToRunMouseWithoutBordersOn); 269 Service.StartMouseWithoutBordersService(desktopToRunMouseWithoutBordersOn); 270 } 271 } 272 273 internal static void CheckForDesktopSwitchEvent(bool cleanupIfExit) 274 { 275 try 276 { 277 if (!IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId()) 278 { 279 Helper.RunDDHelper(true); 280 int waitCount = 20; 281 282 while (NativeMethods.WTSGetActiveConsoleSessionId() == 0xFFFFFFFF && waitCount > 0) 283 { 284 waitCount--; 285 Logger.LogDebug("The session is detached/attached."); 286 Thread.Sleep(500); 287 } 288 289 string myDesktop = GetMyDesktop(); 290 activeDesktop = GetInputDesktop(); 291 292 Logger.LogDebug("*** Active Desktop = " + activeDesktop); 293 Logger.LogDebug("*** My Desktop = " + myDesktop); 294 295 if (myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase)) 296 { 297 Logger.LogDebug("*** Active Desktop == My Desktop (TS session)"); 298 } 299 300 if (!activeDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) && 301 !activeDesktop.Equals("default", StringComparison.OrdinalIgnoreCase) && 302 !activeDesktop.Equals("disconnect", StringComparison.OrdinalIgnoreCase)) 303 { 304 try 305 { 306 StartMMService(activeDesktop); 307 } 308 catch (Exception e) 309 { 310 Logger.Log($"{nameof(CheckForDesktopSwitchEvent)}: {e}"); 311 } 312 } 313 else 314 { 315 if (!myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase)) 316 { 317 Logger.Log("*** Active Desktop <> My Desktop"); 318 } 319 320 uint sid = NativeMethods.WTSGetActiveConsoleSessionId(); 321 322 if (Process.GetProcessesByName(Common.BinaryName).Any(p => (uint)p.SessionId == sid)) 323 { 324 Logger.Log("Found MouseWithoutBorders on the active session!"); 325 } 326 else 327 { 328 Logger.Log("MouseWithoutBorders not found on the active session!"); 329 StartMMService(null); 330 } 331 } 332 333 if (!myDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) && 334 !myDesktop.Equals("default", StringComparison.OrdinalIgnoreCase)) 335 { 336 Logger.LogDebug("*** Desktop inactive, exiting: " + myDesktop); 337 Setting.Values.LastX = Common.JUST_GOT_BACK_FROM_SCREEN_SAVER; 338 if (cleanupIfExit) 339 { 340 InitAndCleanup.Cleanup(); 341 } 342 343 Process.GetCurrentProcess().KillProcess(); 344 } 345 } 346 } 347 catch (Exception e) 348 { 349 Logger.Log(e); 350 } 351 } 352 353 private static Point p; 354 355 internal static bool IsMyDesktopActive() 356 { 357 return NativeMethods.GetCursorPos(ref p); 358 } 359 }