149 lines
5.7 KiB
C#
149 lines
5.7 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace CursorLang.Interop;
|
|
|
|
/// <summary>
|
|
/// Win32 API для окна-подсказки: стили, позиционирование у курсора
|
|
/// и масштаб монитора, на котором курсор находится.
|
|
/// </summary>
|
|
internal static class PopupWindowNative
|
|
{
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct Point
|
|
{
|
|
public int X;
|
|
public int Y;
|
|
}
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool GetCursorPos(out Point lpPoint);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr GetForegroundWindow();
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
|
|
int X, int Y, int cx, int cy, uint uFlags);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr MonitorFromPoint(Point pt, uint dwFlags);
|
|
|
|
[DllImport("shcore.dll")]
|
|
private static extern int GetDpiForMonitor(IntPtr hMonitor, int dpiType, out uint dpiX, out uint dpiY);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr MonitorFromWindow(IntPtr hWnd, uint dwFlags);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfo lpmi);
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct Rect
|
|
{
|
|
public int Left;
|
|
public int Top;
|
|
public int Right;
|
|
public int Bottom;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct MonitorInfo
|
|
{
|
|
public int cbSize;
|
|
public Rect rcMonitor;
|
|
public Rect rcWork;
|
|
public uint dwFlags;
|
|
}
|
|
|
|
private const int GWL_EXSTYLE = -20;
|
|
// Окно не забирает фокус у активного приложения
|
|
private const int WS_EX_NOACTIVATE = 0x08000000;
|
|
// И не попадает в Alt+Tab
|
|
private const int WS_EX_TOOLWINDOW = 0x00000080;
|
|
|
|
private const uint SWP_NOSIZE = 0x0001;
|
|
private const uint SWP_NOZORDER = 0x0004;
|
|
private const uint SWP_NOACTIVATE = 0x0010;
|
|
|
|
private const uint MONITOR_DEFAULTTONEAREST = 2;
|
|
private const int MDT_EFFECTIVE_DPI = 0;
|
|
|
|
internal static Point GetCursorPosition()
|
|
{
|
|
GetCursorPos(out Point cursor);
|
|
return cursor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Подсказка всплывает поверх чужих приложений, поэтому она не должна
|
|
/// ни активироваться сама, ни отбирать фокус ввода у активного окна.
|
|
/// </summary>
|
|
internal static void MakePassive(IntPtr hWnd)
|
|
{
|
|
int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
|
|
SetWindowLong(hWnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Двигает окно в точку экрана, не меняя размер и порядок окон.
|
|
/// Координаты — физические пиксели: у мониторов разный масштаб, а
|
|
/// Window.Left/Top пересчитываются по DPI того монитора, где окно сейчас,
|
|
/// и на соседнем мониторе дают промах.
|
|
/// </summary>
|
|
internal static void MoveTo(IntPtr hWnd, int x, int y)
|
|
{
|
|
SetWindowPos(hWnd, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Задаёт положение и размер окна в физических пикселях.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Размер выставляется именно так, а не через Width/Height: при первом показе
|
|
/// окно ещё подчиняется системному минимальному размеру окна (SM_CXMIN×SM_CYMIN)
|
|
/// и подсказка выходит заметно крупнее текста. К моменту вызова окно уже
|
|
/// показано и стало popup-окном, на которое это ограничение не действует.
|
|
/// </remarks>
|
|
internal static void SetBounds(IntPtr hWnd, int x, int y, int width, int height)
|
|
{
|
|
SetWindowPos(hWnd, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
|
|
/// <summary>Масштаб монитора, на котором находится точка (1.0 при 96 DPI).</summary>
|
|
internal static double GetScaleAt(Point point) =>
|
|
GetScaleOf(MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST));
|
|
|
|
/// <summary>
|
|
/// Рабочая область монитора с активным окном — без панели задач — и его масштаб.
|
|
/// Именно на этом мониторе пользователь сейчас работает.
|
|
/// </summary>
|
|
internal static (Rect WorkArea, double Scale) GetActiveMonitorWorkArea()
|
|
{
|
|
IntPtr monitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTONEAREST);
|
|
|
|
var info = new MonitorInfo { cbSize = Marshal.SizeOf<MonitorInfo>() };
|
|
if (!GetMonitorInfo(monitor, ref info))
|
|
{
|
|
return (new Rect(), 1.0);
|
|
}
|
|
|
|
return (info.rcWork, GetScaleOf(monitor));
|
|
}
|
|
|
|
private static double GetScaleOf(IntPtr monitor)
|
|
{
|
|
if (GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, out uint dpiX, out _) < 0)
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
return dpiX / 96.0;
|
|
}
|
|
}
|