/ src / modules / MouseWithoutBorders / App / Core / WinAPI.cs
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  }