2023-07-12 22:41:31 +00:00
|
|
|
|
using MSFSPopoutPanelManager.DomainModel.Profile;
|
2022-07-23 19:23:32 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Drawing;
|
2023-07-12 22:41:31 +00:00
|
|
|
|
using System.Globalization;
|
2022-07-23 19:23:32 +00:00
|
|
|
|
using System.Runtime.InteropServices;
|
2023-07-12 22:41:31 +00:00
|
|
|
|
using System.Threading;
|
2023-07-23 05:13:23 +00:00
|
|
|
|
using System.Windows.Forms;
|
2022-07-23 19:23:32 +00:00
|
|
|
|
|
|
|
|
|
namespace MSFSPopoutPanelManager.WindowsAgent
|
|
|
|
|
{
|
|
|
|
|
public class WindowActionManager
|
|
|
|
|
{
|
|
|
|
|
public static event EventHandler<bool> OnPopOutManagerAlwaysOnTopChanged;
|
|
|
|
|
|
|
|
|
|
public static void ApplyHidePanelTitleBar(IntPtr hwnd, bool hideTitleBar)
|
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
if (hideTitleBar)
|
|
|
|
|
{
|
|
|
|
|
var rect = WindowActionManager.GetWindowRectangle(hwnd);
|
|
|
|
|
var rectShadow = PInvoke.GetWindowRectShadow(hwnd);
|
|
|
|
|
|
|
|
|
|
MoveWindow(hwnd, rect.Left - rectShadow.Left, rect.Top, rect.Width - rectShadow.Width, rect.Height - rectShadow.Height);
|
|
|
|
|
Thread.Sleep(250);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-02 20:55:20 +00:00
|
|
|
|
var currentStyle = (uint)PInvoke.GetWindowLong(hwnd, PInvokeConstant.GWL_STYLE);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
|
|
|
|
|
if (hideTitleBar)
|
2022-08-02 20:55:20 +00:00
|
|
|
|
currentStyle &= ~(PInvokeConstant.WS_CAPTION | PInvokeConstant.WS_SIZEBOX);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
else
|
2022-08-02 20:55:20 +00:00
|
|
|
|
currentStyle |= (PInvokeConstant.WS_CAPTION | PInvokeConstant.WS_SIZEBOX);
|
|
|
|
|
|
|
|
|
|
PInvoke.SetWindowLong(hwnd, PInvokeConstant.GWL_STYLE, currentStyle);
|
2023-07-12 22:41:31 +00:00
|
|
|
|
Thread.Sleep(250);
|
|
|
|
|
|
|
|
|
|
if (!hideTitleBar)
|
|
|
|
|
{
|
|
|
|
|
var rect = WindowActionManager.GetWindowRectangle(hwnd);
|
|
|
|
|
var rectShadow = PInvoke.GetWindowRectShadow(hwnd);
|
|
|
|
|
MoveWindow(hwnd, rect.Left + rectShadow.Left, rect.Top, rect.Width + rectShadow.Width, rect.Height + rectShadow.Height);
|
|
|
|
|
}
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 22:41:31 +00:00
|
|
|
|
public static void ApplyAlwaysOnTop(IntPtr hwnd, PanelType panelType, bool alwaysOnTop)
|
2022-07-23 19:23:32 +00:00
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var rectangle = WindowActionManager.GetWindowRectangle(hwnd);
|
|
|
|
|
|
2022-07-23 19:23:32 +00:00
|
|
|
|
if (panelType == PanelType.PopOutManager)
|
|
|
|
|
{
|
|
|
|
|
OnPopOutManagerAlwaysOnTopChanged?.Invoke(null, alwaysOnTop);
|
2023-07-12 22:41:31 +00:00
|
|
|
|
return;
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
2023-07-12 22:41:31 +00:00
|
|
|
|
|
|
|
|
|
if (alwaysOnTop)
|
|
|
|
|
PInvoke.SetWindowPos(hwnd, new IntPtr(PInvokeConstant.HWND_TOPMOST), 0, 0, 0, 0, PInvokeConstant.SWP_ALWAYS_ON_TOP);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
PInvoke.SetWindowPos(hwnd, new IntPtr(PInvokeConstant.HWND_NOTOPMOST), rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height, 0);
|
|
|
|
|
Thread.Sleep(250);
|
|
|
|
|
MoveWindow(hwnd, rectangle.Left, rectangle.Top, rectangle.Width, rectangle.Height);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 22:41:31 +00:00
|
|
|
|
public static IntPtr FindWindowByClass(string className)
|
2022-07-23 19:23:32 +00:00
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
return PInvoke.FindWindow(className, null);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void CloseWindow(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
PInvoke.SendMessage(hwnd, PInvokeConstant.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-13 06:14:49 +00:00
|
|
|
|
public static void MoveWindow(IntPtr hwnd, int x, int y, int width, int height)
|
2022-07-23 19:23:32 +00:00
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var rectShadow = PInvoke.GetWindowRectShadow(hwnd);
|
|
|
|
|
PInvoke.MoveWindow(hwnd, x + rectShadow.Left, y + rectShadow.Top, width + rectShadow.Width, height + rectShadow.Height, true);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 22:41:31 +00:00
|
|
|
|
public static void MoveWindow(IntPtr hwnd, Rectangle rect)
|
2022-07-23 19:23:32 +00:00
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var rectShadow = PInvoke.GetWindowRectShadow(hwnd);
|
|
|
|
|
PInvoke.MoveWindow(hwnd, rect.Left + rectShadow.Left, rect.Top + rectShadow.Top, rect.Width + rectShadow.Width, rect.Height + rectShadow.Height, true);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void MinimizeWindow(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_MINIMIZE);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-29 06:11:56 +00:00
|
|
|
|
public static void RestoreWindow(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-23 19:23:32 +00:00
|
|
|
|
public static void BringWindowToForeground(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
PInvoke.ShowWindowAsync(new HandleRef(null, hwnd), PInvokeConstant.SW_RESTORE);
|
|
|
|
|
PInvoke.SetForegroundWindow(hwnd);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 22:41:31 +00:00
|
|
|
|
public static void SetWindowFocus(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
var style = (uint) PInvoke.GetWindowLong(hwnd, PInvokeConstant.GWL_STYLE);
|
|
|
|
|
|
|
|
|
|
// Minimize and restore to be able to make it active.
|
|
|
|
|
if ((style & PInvokeConstant.SW_MINIMIZE) == PInvokeConstant.SW_MINIMIZE)
|
|
|
|
|
{
|
|
|
|
|
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint currentlyFocusedWindowProcessId = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow(), IntPtr.Zero);
|
|
|
|
|
uint appThread = PInvoke.GetCurrentThreadId();
|
|
|
|
|
|
|
|
|
|
if (currentlyFocusedWindowProcessId != appThread)
|
|
|
|
|
{
|
|
|
|
|
PInvoke.AttachThreadInput(currentlyFocusedWindowProcessId, appThread, true);
|
|
|
|
|
PInvoke.BringWindowToTop(hwnd);
|
|
|
|
|
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_SHOW);
|
|
|
|
|
PInvoke.AttachThreadInput(currentlyFocusedWindowProcessId, appThread, false);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
PInvoke.BringWindowToTop(hwnd);
|
|
|
|
|
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_SHOW);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-06 04:07:03 +00:00
|
|
|
|
public static string GetWindowCaption(IntPtr hwnd)
|
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
try { return PInvoke.GetWindowText(hwnd); }
|
|
|
|
|
catch { return string.Empty; }
|
2022-09-06 04:07:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 22:41:31 +00:00
|
|
|
|
public static void SetWindowCaption(IntPtr hwnd, string caption)
|
2022-07-23 19:23:32 +00:00
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
PInvoke.SetWindowText(hwnd, caption);
|
|
|
|
|
}
|
2022-07-23 19:23:32 +00:00
|
|
|
|
|
2023-07-12 22:41:31 +00:00
|
|
|
|
public static Rectangle GetWindowRectangle(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
return PInvoke.GetWindowRectangleDwm(hwnd);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-06 04:07:03 +00:00
|
|
|
|
public static List<IntPtr> GetWindowsByPanelType(List<PanelType> panelTypes)
|
|
|
|
|
{
|
2024-02-28 02:44:21 +00:00
|
|
|
|
var windowHandles = new List<IntPtr>();
|
2022-09-06 04:07:03 +00:00
|
|
|
|
|
2024-02-28 02:44:21 +00:00
|
|
|
|
PInvoke.EnumWindows((hwnd, _) =>
|
2022-09-06 04:07:03 +00:00
|
|
|
|
{
|
|
|
|
|
var panelType = GetWindowPanelType(hwnd);
|
|
|
|
|
|
|
|
|
|
if (panelTypes.Contains(panelType))
|
|
|
|
|
windowHandles.Add(hwnd);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
return windowHandles;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-23 19:23:32 +00:00
|
|
|
|
public static PanelType GetWindowPanelType(IntPtr hwnd)
|
|
|
|
|
{
|
|
|
|
|
var className = PInvoke.GetClassName(hwnd);
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var caption = GetWindowCaption(hwnd);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
|
|
|
|
|
if (className == "AceApp") // MSFS windows designation
|
|
|
|
|
{
|
2024-02-28 02:44:21 +00:00
|
|
|
|
if (string.IsNullOrEmpty(caption) || caption.IndexOf("(Custom)", StringComparison.Ordinal) > -1) // Pop Out window
|
2022-07-23 19:23:32 +00:00
|
|
|
|
return PanelType.CustomPopout;
|
2024-02-28 02:44:21 +00:00
|
|
|
|
|
|
|
|
|
if (caption.IndexOf("Microsoft Flight Simulator", StringComparison.Ordinal) > -1) // MSFS main game window
|
2022-07-23 19:23:32 +00:00
|
|
|
|
return PanelType.FlightSimMainWindow;
|
2024-02-28 02:44:21 +00:00
|
|
|
|
|
|
|
|
|
if (caption.IndexOf("WINDOW", StringComparison.Ordinal) > -1) // MSFS multi-monitor window
|
2022-07-23 19:23:32 +00:00
|
|
|
|
return PanelType.MultiMonitorWindow;
|
2024-02-28 02:44:21 +00:00
|
|
|
|
|
|
|
|
|
return PanelType.BuiltInPopout; // MSFS built-in window such as ATC, VFRMap
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
2024-02-28 02:44:21 +00:00
|
|
|
|
|
|
|
|
|
if (className.IndexOf("HwndWrapper[MSFSPopoutPanelManager", StringComparison.Ordinal) > -1)
|
2022-07-23 19:23:32 +00:00
|
|
|
|
{
|
2024-02-28 02:44:21 +00:00
|
|
|
|
if (caption.IndexOf("HudBar", StringComparison.Ordinal) > -1)
|
2023-07-12 22:41:31 +00:00
|
|
|
|
return PanelType.HudBarWindow;
|
2024-02-28 02:44:21 +00:00
|
|
|
|
|
|
|
|
|
if (caption.IndexOf("Virtual NumPad", StringComparison.Ordinal) > -1)
|
|
|
|
|
return PanelType.NumPadWindow;
|
|
|
|
|
|
|
|
|
|
return PanelType.PopOutManager;
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PanelType.Unknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void CloseAllPopOuts()
|
|
|
|
|
{
|
2024-02-28 02:44:21 +00:00
|
|
|
|
PInvoke.EnumWindows((hwnd, _) =>
|
2022-07-23 19:23:32 +00:00
|
|
|
|
{
|
|
|
|
|
var panelType = GetWindowPanelType(hwnd);
|
|
|
|
|
|
2024-02-28 02:44:21 +00:00
|
|
|
|
if (panelType == PanelType.CustomPopout || panelType == PanelType.HudBarWindow || panelType == PanelType.NumPadWindow)
|
2022-09-06 04:07:03 +00:00
|
|
|
|
CloseWindow(hwnd);
|
2022-07-23 19:23:32 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}, 0);
|
|
|
|
|
}
|
2022-09-06 04:07:03 +00:00
|
|
|
|
|
|
|
|
|
public static MsfsGameWindowConfig GetMsfsGameWindowLocation()
|
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var isWindowedMode = IsMsfsGameInWindowedMode();
|
2022-09-06 04:07:03 +00:00
|
|
|
|
|
|
|
|
|
if (isWindowedMode)
|
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var msfsGameWindowHandle = GetMsfsGameWindowHandle();
|
|
|
|
|
var rect = WindowActionManager.GetWindowRectangle(msfsGameWindowHandle);
|
|
|
|
|
return new MsfsGameWindowConfig(rect.Left, rect.Top, rect.Width, rect.Height);
|
2022-09-06 04:07:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new MsfsGameWindowConfig(0, 0, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void SetMsfsGameWindowLocation(MsfsGameWindowConfig msfsGameWindowConfig)
|
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var isWindowedMode = IsMsfsGameInWindowedMode();
|
2022-09-06 04:07:03 +00:00
|
|
|
|
|
|
|
|
|
if (isWindowedMode && msfsGameWindowConfig.IsValid)
|
2023-07-12 22:41:31 +00:00
|
|
|
|
{
|
|
|
|
|
var msfsGameWindowHandle = GetMsfsGameWindowHandle();
|
2022-09-06 04:07:03 +00:00
|
|
|
|
PInvoke.MoveWindow(msfsGameWindowHandle, msfsGameWindowConfig.Left, msfsGameWindowConfig.Top, msfsGameWindowConfig.Width, msfsGameWindowConfig.Height, true);
|
2023-07-12 22:41:31 +00:00
|
|
|
|
}
|
2022-09-06 04:07:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 22:41:31 +00:00
|
|
|
|
public static bool IsMsfsGameInWindowedMode()
|
2022-09-06 04:07:03 +00:00
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var msfsGameWindowHandle = GetMsfsGameWindowHandle();
|
|
|
|
|
|
2022-09-06 04:07:03 +00:00
|
|
|
|
if (msfsGameWindowHandle != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
var currentStyle = (uint)PInvoke.GetWindowLong(msfsGameWindowHandle, PInvokeConstant.GWL_STYLE);
|
|
|
|
|
return (currentStyle & PInvokeConstant.WS_CAPTION) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static IntPtr GetMsfsGameWindowHandle()
|
|
|
|
|
{
|
|
|
|
|
IntPtr msfsGameWindowHandle = IntPtr.Zero;
|
|
|
|
|
|
|
|
|
|
// Get game window handle
|
2024-02-28 02:44:21 +00:00
|
|
|
|
PInvoke.EnumWindows((hwnd, _) =>
|
2022-09-06 04:07:03 +00:00
|
|
|
|
{
|
|
|
|
|
var className = PInvoke.GetClassName(hwnd);
|
|
|
|
|
|
|
|
|
|
if (className == "AceApp") // MSFS windows designation
|
|
|
|
|
{
|
2023-07-12 22:41:31 +00:00
|
|
|
|
var caption = GetWindowCaption(hwnd);
|
2022-09-06 04:07:03 +00:00
|
|
|
|
|
2024-02-28 02:44:21 +00:00
|
|
|
|
if (caption.IndexOf("Microsoft Flight Simulator", StringComparison.Ordinal) > -1) // MSFS main game window
|
2022-09-06 04:07:03 +00:00
|
|
|
|
{
|
|
|
|
|
msfsGameWindowHandle = hwnd;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2024-02-28 02:44:21 +00:00
|
|
|
|
}, 0);
|
2022-09-06 04:07:03 +00:00
|
|
|
|
|
|
|
|
|
return msfsGameWindowHandle;
|
|
|
|
|
}
|
2023-07-12 22:41:31 +00:00
|
|
|
|
|
|
|
|
|
public static void SetWindowTitleBarColor(IntPtr hwnd, string hexColor)
|
|
|
|
|
{
|
2024-02-28 02:44:21 +00:00
|
|
|
|
if (int.TryParse(hexColor, NumberStyles.HexNumber, null, out var color))
|
2023-07-12 22:41:31 +00:00
|
|
|
|
PInvoke.DwmSetWindowAttribute(hwnd, DwmWindowAttribute.DWMWA_CAPTION_COLOR, ref color, sizeof(Int32));
|
|
|
|
|
}
|
2023-07-23 05:13:23 +00:00
|
|
|
|
|
|
|
|
|
public static List<MonitorInfo> GetMonitorsInfo()
|
|
|
|
|
{
|
|
|
|
|
var monitors = new List<MonitorInfo>();
|
|
|
|
|
|
2024-02-28 02:44:21 +00:00
|
|
|
|
foreach (var screen in Screen.AllScreens)
|
|
|
|
|
monitors.Add(new MonitorInfo { Name = screen.DeviceName.Substring(screen.DeviceName.LastIndexOf("\\", StringComparison.Ordinal) + 1), X = screen.Bounds.X, Y = screen.Bounds.Y, Width = screen.Bounds.Width, Height = screen.Bounds.Height });
|
|
|
|
|
|
2023-07-23 05:13:23 +00:00
|
|
|
|
return monitors;
|
|
|
|
|
}
|
2024-02-28 02:44:21 +00:00
|
|
|
|
|
|
|
|
|
public static bool IsPointInsideAppWindow(Point point)
|
|
|
|
|
{
|
|
|
|
|
var appHandle = WindowProcessManager.GetApplicationProcess().Handle;
|
|
|
|
|
|
|
|
|
|
if (appHandle == IntPtr.Zero)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
var rect = PInvoke.GetWindowRectangleDwm(appHandle);
|
|
|
|
|
|
|
|
|
|
var rightEdge = rect.X + rect.Width;
|
|
|
|
|
var bottomEdge = rect.Y + rect.Height;
|
|
|
|
|
|
|
|
|
|
return point.X >= rect.X && point.X <= rightEdge && point.Y >= rect.Y && point.Y <= bottomEdge;
|
|
|
|
|
|
|
|
|
|
}
|
2022-07-23 19:23:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|