using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
namespace XFEExtension.NetCore.InputSimulator.Native;
internal static class WindowsDesktop
{
internal static void EnsureWindows()
{
if (!OperatingSystem.IsWindows())
throw new PlatformNotSupportedException("系统输入和屏幕查询仅支持 Windows。");
}
internal static Size ScreenSize()
{
EnsureWindows();
return new Size(NativeMethods.GetSystemMetrics(0), NativeMethods.GetSystemMetrics(1));
}
internal static Rectangle Bounds(bool virtualDesktop)
{
using var dpi = new PhysicalCoordinates();
return virtualDesktop
? new Rectangle(NativeMethods.GetSystemMetrics(76), NativeMethods.GetSystemMetrics(77),
NativeMethods.GetSystemMetrics(78), NativeMethods.GetSystemMetrics(79))
: new Rectangle(Point.Empty, ScreenSize());
}
internal static Point CursorPosition()
{
using var dpi = new PhysicalCoordinates();
if (!NativeMethods.GetCursorPos(out var point))
throw new Win32Exception(Marshal.GetLastPInvokeError());
return point;
}
internal static Point Normalize(Point point, Rectangle bounds)
{
if (bounds.Width <= 0 || bounds.Height <= 0)
throw new InvalidOperationException("没有可用的屏幕区域。");
// 选取目标像素在 0..65535 区间内的中心,支持虚拟桌面的负坐标。
static int Axis(int value, int origin, int length) =>
(int)Math.Min(65535, (Math.Clamp((long)value - origin, 0, length - 1) * 65536 + 32768) / length);
return new Point(Axis(point.X, bounds.X, bounds.Width), Axis(point.Y, bounds.Y, bounds.Height));
}
internal static Point Position(ScreenPosition position, Rectangle bounds) => position switch
{
ScreenPosition.TopLeft => bounds.Location,
ScreenPosition.Top => new(bounds.Left + bounds.Width / 2, bounds.Top),
ScreenPosition.TopRight => new(bounds.Right - 1, bounds.Top),
ScreenPosition.Left => new(bounds.Left, bounds.Top + bounds.Height / 2),
ScreenPosition.Center => new(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2),
ScreenPosition.Right => new(bounds.Right - 1, bounds.Top + bounds.Height / 2),
ScreenPosition.BottomLeft => new(bounds.Left, bounds.Bottom - 1),
ScreenPosition.Bottom => new(bounds.Left + bounds.Width / 2, bounds.Bottom - 1),
ScreenPosition.BottomRight => new(bounds.Right - 1, bounds.Bottom - 1),
_ => throw new ArgumentOutOfRangeException(nameof(position))
};
// 只在同步查询期间修改当前线程的 DPI 上下文,退出时恢复,不改变宿主进程设置。
private readonly struct PhysicalCoordinates : IDisposable
{
private readonly nint previous;
public PhysicalCoordinates()
{
EnsureWindows();
previous = NativeMethods.SetThreadDpiAwarenessContext(-4);
if (previous == 0)
throw new Win32Exception(Marshal.GetLastPInvokeError(), "无法获取物理屏幕坐标;需要 Windows 10 1703 或更高版本。");
}
public void Dispose() => NativeMethods.SetThreadDpiAwarenessContext(previous);
}
}
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
namespace XFEExtension.NetCore.InputSimulator.Native;
internal static class WindowsDesktop
{
internal static void EnsureWindows()
{
if (!OperatingSystem.IsWindows())
throw new PlatformNotSupportedException("系统输入和屏幕查询仅支持 Windows。");
}
internal static Size ScreenSize()
{
EnsureWindows();
return new Size(NativeMethods.GetSystemMetrics(0), NativeMethods.GetSystemMetrics(1));
}
internal static Rectangle Bounds(bool virtualDesktop)
{
using var dpi = new PhysicalCoordinates();
return virtualDesktop
? new Rectangle(NativeMethods.GetSystemMetrics(76), NativeMethods.GetSystemMetrics(77),
NativeMethods.GetSystemMetrics(78), NativeMethods.GetSystemMetrics(79))
: new Rectangle(Point.Empty, ScreenSize());
}
internal static Point CursorPosition()
{
using var dpi = new PhysicalCoordinates();
if (!NativeMethods.GetCursorPos(out var point))
throw new Win32Exception(Marshal.GetLastPInvokeError());
return point;
}
internal static Point Normalize(Point point, Rectangle bounds)
{
if (bounds.Width <= 0 || bounds.Height <= 0)
throw new InvalidOperationException("没有可用的屏幕区域。");
// 选取目标像素在 0..65535 区间内的中心,支持虚拟桌面的负坐标。
static int Axis(int value, int origin, int length) =>
(int)Math.Min(65535, (Math.Clamp((long)value - origin, 0, length - 1) * 65536 + 32768) / length);
return new Point(Axis(point.X, bounds.X, bounds.Width), Axis(point.Y, bounds.Y, bounds.Height));
}
internal static Point Position(ScreenPosition position, Rectangle bounds) => position switch
{
ScreenPosition.TopLeft => bounds.Location,
ScreenPosition.Top => new(bounds.Left + bounds.Width / 2, bounds.Top),
ScreenPosition.TopRight => new(bounds.Right - 1, bounds.Top),
ScreenPosition.Left => new(bounds.Left, bounds.Top + bounds.Height / 2),
ScreenPosition.Center => new(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2),
ScreenPosition.Right => new(bounds.Right - 1, bounds.Top + bounds.Height / 2),
ScreenPosition.BottomLeft => new(bounds.Left, bounds.Bottom - 1),
ScreenPosition.Bottom => new(bounds.Left + bounds.Width / 2, bounds.Bottom - 1),
ScreenPosition.BottomRight => new(bounds.Right - 1, bounds.Bottom - 1),
_ => throw new ArgumentOutOfRangeException(nameof(position))
};
// 只在同步查询期间修改当前线程的 DPI 上下文,退出时恢复,不改变宿主进程设置。
private readonly struct PhysicalCoordinates : IDisposable
{
private readonly nint previous;
public PhysicalCoordinates()
{
EnsureWindows();
previous = NativeMethods.SetThreadDpiAwarenessContext(-4);
if (previous == 0)
throw new Win32Exception(Marshal.GetLastPInvokeError(), "无法获取物理屏幕坐标;需要 Windows 10 1703 或更高版本。");
}
public void Dispose() => NativeMethods.SetThreadDpiAwarenessContext(previous);
}
}