fixed comment into code
This commit is contained in:
@@ -6,12 +6,12 @@ using Accessibility;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Определяет положение каретки в активном поле ввода — в том числе в чужом приложении.
|
||||
/// Locates the caret in the active input field — including one in another application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Единого способа нет: классические Win32-приложения заводят системную каретку,
|
||||
/// а Chrome, Electron и прочие рисуют её сами и сообщают положение только через
|
||||
/// средства доступности. Поэтому сначала спрашиваем систему, затем — приложение.
|
||||
/// There is no single way to do it: classic Win32 applications create a system caret,
|
||||
/// while Chrome, Electron and others draw it themselves and report its position only
|
||||
/// through accessibility interfaces. So we ask the system first, then the application.
|
||||
/// </remarks>
|
||||
internal static class CaretNative
|
||||
{
|
||||
@@ -32,8 +32,8 @@ internal static class CaretNative
|
||||
private const int CHILDID_SELF = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Прямоугольник каретки в пикселях экрана или <c>null</c>, если активное
|
||||
/// приложение не сообщает её положение.
|
||||
/// The caret rectangle in screen pixels, or <c>null</c> when the active
|
||||
/// application does not report its position.
|
||||
/// </summary>
|
||||
internal static PopupWindowNative.Rect? TryGetCaretRect()
|
||||
{
|
||||
@@ -50,13 +50,13 @@ internal static class CaretNative
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отсеивает заведомо неверные координаты.
|
||||
/// Filters out obviously wrong coordinates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Часть приложений отдаёт положение каретки в своей системе координат либо
|
||||
/// без учёта масштаба экрана, и подсказка уезжает далеко от поля ввода.
|
||||
/// Каретка обязана находиться внутри окна ввода — это и проверяем, а перед
|
||||
/// отказом пробуем истолковать координаты как немасштабированные.
|
||||
/// Some applications report the caret position in their own coordinate system or
|
||||
/// without accounting for display scaling, and the popup ends up far from the input
|
||||
/// field. The caret must be inside the input window — that is what we check, and
|
||||
/// before giving up we try to read the coordinates as unscaled ones.
|
||||
/// </remarks>
|
||||
private static PopupWindowNative.Rect? Validate(PopupWindowNative.Rect caret, IntPtr hwndFocus)
|
||||
{
|
||||
@@ -86,16 +86,16 @@ internal static class CaretNative
|
||||
inner.Left >= outer.Left && inner.Right <= outer.Right &&
|
||||
inner.Top >= outer.Top && inner.Bottom <= outer.Bottom;
|
||||
|
||||
/// <summary>Сколько ждём ответа от чужого приложения по UI Automation.</summary>
|
||||
/// <summary>How long we wait for another application to answer over UI Automation.</summary>
|
||||
private static readonly TimeSpan AutomationTimeout = TimeSpan.FromMilliseconds(150);
|
||||
|
||||
// Браузеры и другие приложения на своих движках рисуют каретку сами и
|
||||
// сообщают её положение только через UI Automation. Запрос идёт в чужой
|
||||
// процесс, поэтому он самый медленный и стоит последним
|
||||
// Browsers and other applications with their own rendering engines draw the caret
|
||||
// themselves and report its position only through UI Automation. The request goes
|
||||
// into another process, so it is the slowest one and comes last
|
||||
private static PopupWindowNative.Rect? TryGetAutomationCaret()
|
||||
{
|
||||
// Зависшее приложение не должно подвешивать подсказку вместе с собой:
|
||||
// ждём ответ ограниченное время, иначе показываем у курсора
|
||||
// A hung application must not hang the popup along with it: we wait for the
|
||||
// answer for a limited time, otherwise we show the popup at the cursor
|
||||
Task<PopupWindowNative.Rect?> query = Task.Run(QueryAutomationCaret);
|
||||
return query.Wait(AutomationTimeout) ? query.Result : null;
|
||||
}
|
||||
@@ -117,8 +117,8 @@ internal static class CaretNative
|
||||
return null;
|
||||
}
|
||||
|
||||
// У каретки выделение пустое и прямоугольника не имеет,
|
||||
// поэтому расширяем его до ближайшего символа
|
||||
// The caret has an empty selection and therefore no rectangle,
|
||||
// so we expand it to the nearest character
|
||||
TextPatternRange range = selection[0].Clone();
|
||||
range.ExpandToEnclosingUnit(TextUnit.Character);
|
||||
|
||||
@@ -141,12 +141,12 @@ internal static class CaretNative
|
||||
or InvalidOperationException
|
||||
or COMException)
|
||||
{
|
||||
// Приложение закрылось или не отвечает — подсказку это ронять не должно
|
||||
// The application closed or stopped responding — that must not take the popup down
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Системная каретка: координаты приходят относительно окна, которому она принадлежит
|
||||
// The system caret: its coordinates come relative to the window that owns it
|
||||
private static PopupWindowNative.Rect? TryGetSystemCaret(ForegroundInputNative.GuiThreadInfo info)
|
||||
{
|
||||
if (info.hwndCaret == IntPtr.Zero || IsEmpty(info.rcCaret))
|
||||
@@ -170,7 +170,7 @@ internal static class CaretNative
|
||||
};
|
||||
}
|
||||
|
||||
// Каретка через средства доступности: сюда попадают браузеры и Electron
|
||||
// The caret through accessibility interfaces: this is where browsers and Electron land
|
||||
private static PopupWindowNative.Rect? TryGetAccessibleCaret(IntPtr hwndFocus)
|
||||
{
|
||||
if (hwndFocus == IntPtr.Zero)
|
||||
@@ -199,7 +199,7 @@ internal static class CaretNative
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Приложение объявило поддержку, но положение не отдало
|
||||
// The application declared support but did not report the position
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
@@ -208,7 +208,7 @@ internal static class CaretNative
|
||||
}
|
||||
}
|
||||
|
||||
// Когда каретки нет, её прямоугольник приходит нулевой высоты.
|
||||
// Судим только по высоте: нулевые координаты — это обычное начало пустого поля
|
||||
private static bool IsEmpty(PopupWindowNative.Rect rect) => rect.Bottom - rect.Top <= 0;
|
||||
// When there is no caret, its rectangle comes back with zero height.
|
||||
// We judge by height alone: zero coordinates are a normal start of an empty field
|
||||
internal static bool IsEmpty(PopupWindowNative.Rect rect) => rect.Bottom - rect.Top <= 0;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Сведения о вводе в активном приложении: какое окно держит фокус клавиатуры
|
||||
/// и где находится каретка.
|
||||
/// Input details of the active application: which window holds keyboard focus
|
||||
/// and where the caret is.
|
||||
/// </summary>
|
||||
internal static class ForegroundInputNative
|
||||
{
|
||||
@@ -26,12 +26,12 @@ internal static class ForegroundInputNative
|
||||
private static extern bool GetGUIThreadInfo(uint idThread, ref GuiThreadInfo lpgui);
|
||||
|
||||
/// <summary>
|
||||
/// Состояние ввода потока переднего плана.
|
||||
/// The input state of the foreground thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Нулевой идентификатор потока не случаен: у современных приложений окно
|
||||
/// верхнего уровня и окно ввода живут в разных потоках, и спрашивать нужно
|
||||
/// именно про передний план целиком.
|
||||
/// The zero thread identifier is not accidental: in modern applications the
|
||||
/// top-level window and the input window live in different threads, and the
|
||||
/// question has to be about the foreground as a whole.
|
||||
/// </remarks>
|
||||
internal static bool TryGetInfo(out GuiThreadInfo info)
|
||||
{
|
||||
|
||||
@@ -3,21 +3,21 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Право выводить окно на передний план.
|
||||
/// The right to bring a window to the foreground.
|
||||
/// </summary>
|
||||
internal static class ForegroundPermissionNative
|
||||
{
|
||||
/// <summary>ASFW_ANY — право получает любой процесс.</summary>
|
||||
/// <summary>ASFW_ANY — any process gets the right.</summary>
|
||||
private const uint AnyProcess = 0xFFFFFFFF;
|
||||
|
||||
/// <summary>
|
||||
/// Уступает своё право вывести окно на передний план другим процессам.
|
||||
/// Gives up our right to bring a window to the foreground in favour of other processes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Поменять окно переднего плана Windows разрешает не всякому: нужно быть
|
||||
/// процессом, с которым пользователь работал последним. Давно запущенный
|
||||
/// экземпляр приложения к таким не относится, и его окно всплывёт только
|
||||
/// если правом поделится процесс, который пользователь запустил только что.
|
||||
/// Windows does not let just anyone change the foreground window: you have to be
|
||||
/// the process the user interacted with last. An instance started long ago is not
|
||||
/// one of those, and its window will come up only if the right is shared by the
|
||||
/// process the user has just launched.
|
||||
/// </remarks>
|
||||
internal static void GrantToAnyProcess() => AllowSetForegroundWindow(AnyProcess);
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Win32 API для определения раскладки активного приложения.
|
||||
/// Win32 API for reading the layout of the active application.
|
||||
/// </summary>
|
||||
internal static class KeyboardLayoutNative
|
||||
{
|
||||
private const uint WmInputLangChangeRequest = 0x0050;
|
||||
|
||||
/// <summary>Взять следующую раскладку из системного списка.</summary>
|
||||
/// <summary>Take the next layout from the system list.</summary>
|
||||
private static readonly IntPtr InputLangChangeForward = new(0x0002);
|
||||
|
||||
/// <summary>HKL_NEXT — та же просьба на языке старых версий Windows.</summary>
|
||||
/// <summary>HKL_NEXT — the same request in the language of older Windows versions.</summary>
|
||||
private static readonly IntPtr HklNext = new(1);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
@@ -28,17 +28,17 @@ internal static class KeyboardLayoutNative
|
||||
private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Раскладка, которой сейчас печатает пользователь.
|
||||
/// The layout the user is currently typing with.
|
||||
/// </summary>
|
||||
internal static int GetActiveLocaleId() => GetLocaleIdOf(GetInputWindow());
|
||||
|
||||
/// <summary>
|
||||
/// Просит активное приложение перейти на следующую раскладку из системного списка.
|
||||
/// Asks the active application to switch to the next layout from the system list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Синтезировать системное сочетание вроде Alt+Shift не годится: его назначение
|
||||
/// пользователь меняет в настройках Windows и может отключить вовсе. Просьба
|
||||
/// сообщением от таких настроек не зависит и работает в чужом процессе.
|
||||
/// Synthesizing a system shortcut such as Alt+Shift will not do: the user can
|
||||
/// reassign it in the Windows settings or turn it off entirely. A request sent as
|
||||
/// a message does not depend on those settings and works in another process.
|
||||
/// </remarks>
|
||||
internal static void RequestNextLayout()
|
||||
{
|
||||
@@ -48,19 +48,19 @@ internal static class KeyboardLayoutNative
|
||||
return;
|
||||
}
|
||||
|
||||
// Оба параметра означают одно и то же: разные версии Windows и разные
|
||||
// библиотеки интерфейса смотрят то на флаг, то на lParam
|
||||
// Both parameters mean the same thing: different Windows versions and different
|
||||
// UI frameworks look either at the flag or at lParam
|
||||
PostMessage(target, WmInputLangChangeRequest, InputLangChangeForward, HklNext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Окно, которому принадлежит ввод с клавиатуры.
|
||||
/// The window that owns keyboard input.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Спрашиваем окно с фокусом клавиатуры, а не окно верхнего плана: у Блокнота
|
||||
/// Windows 11, меню «Пуск» и прочих приложений на WinUI поле ввода живёт в
|
||||
/// отдельном потоке, и раскладка меняется только у него. У потока главного
|
||||
/// окна она остаётся прежней, и переключение проходит незамеченным.
|
||||
/// We ask the window with keyboard focus rather than the foreground window: in
|
||||
/// Windows 11 Notepad, the Start menu and other WinUI applications the input field
|
||||
/// lives in a separate thread, and the layout changes only for that thread. For the
|
||||
/// main window's thread it stays the same, and the switch goes unnoticed.
|
||||
/// </remarks>
|
||||
private static IntPtr GetInputWindow()
|
||||
{
|
||||
@@ -74,8 +74,8 @@ internal static class KeyboardLayoutNative
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор локали для окна. Раскладка в Windows привязана к потоку,
|
||||
/// поэтому так её видно у любого приложения, а не только у своего.
|
||||
/// The locale identifier for a window. In Windows the layout is bound to a thread,
|
||||
/// so this reveals it for any application, not only for our own.
|
||||
/// </summary>
|
||||
internal static int GetLocaleIdOf(IntPtr hWnd)
|
||||
{
|
||||
|
||||
@@ -3,22 +3,22 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Системный перехватчик клавиатуры (WH_KEYBOARD_LL): видит нажатия во всех
|
||||
/// приложениях и умеет не пропускать их дальше.
|
||||
/// A system keyboard hook (WH_KEYBOARD_LL): sees key presses in every application
|
||||
/// and can keep them from going any further.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ставится только низкоуровневый хук: обычный WH_KEYBOARD требует внедрения
|
||||
/// DLL в чужие процессы, что для managed-кода невозможно. Обратный вызов
|
||||
/// приходит на тот поток, который поставил хук, и поток обязан крутить цикл
|
||||
/// сообщений — отсюда требование ставить хук из потока пользовательского
|
||||
/// интерфейса. Возврат управления затягивать нельзя: по истечении системного
|
||||
/// тайм-аута Windows молча снимает хук.
|
||||
/// Only the low-level hook is installed: a regular WH_KEYBOARD requires injecting a
|
||||
/// DLL into other processes, which is impossible for managed code. The callback
|
||||
/// arrives on the thread that installed the hook, and that thread must pump a message
|
||||
/// loop — hence the requirement to install the hook from the user interface thread.
|
||||
/// Returning control must not be delayed: once the system timeout expires, Windows
|
||||
/// silently removes the hook.
|
||||
/// </remarks>
|
||||
internal sealed class LowLevelKeyboardHook : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Обработчик события клавиши. Возвращает <c>true</c>, если событие нужно
|
||||
/// проглотить — тогда приложение переднего плана его не увидит.
|
||||
/// A key event handler. Returns <c>true</c> when the event must be swallowed —
|
||||
/// then the foreground application will not see it.
|
||||
/// </summary>
|
||||
internal delegate bool KeyFilter(int virtualKey, bool isKeyDown);
|
||||
|
||||
@@ -30,14 +30,14 @@ internal sealed class LowLevelKeyboardHook : IDisposable
|
||||
private const int WmSysKeyDown = 0x0104;
|
||||
private const int WmSysKeyUp = 0x0105;
|
||||
|
||||
/// <summary>Событие пришло не от живого нажатия, а из SendInput.</summary>
|
||||
/// <summary>The event came from SendInput rather than from a real key press.</summary>
|
||||
private const uint LowLevelKeyHookFlagInjected = 0x10;
|
||||
|
||||
private readonly KeyFilter _filter;
|
||||
|
||||
// Делегат живёт в поле не для удобства: ссылку на него держит только Win32,
|
||||
// о которой сборщик мусора не знает, и без поля хук перестаёт работать
|
||||
// через случайное время
|
||||
// The delegate lives in a field not for convenience: the only reference to it is
|
||||
// held by Win32, which the garbage collector knows nothing about, and without the
|
||||
// field the hook stops working after a random amount of time
|
||||
private readonly HookProc _callback;
|
||||
|
||||
private IntPtr _handle;
|
||||
@@ -53,7 +53,7 @@ internal sealed class LowLevelKeyboardHook : IDisposable
|
||||
internal bool IsInstalled => _handle != IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Ставит хук. Возвращает <c>false</c>, если система отказала.
|
||||
/// Installs the hook. Returns <c>false</c> when the system refuses.
|
||||
/// </summary>
|
||||
internal bool Install()
|
||||
{
|
||||
@@ -104,14 +104,14 @@ internal sealed class LowLevelKeyboardHook : IDisposable
|
||||
|
||||
var data = Marshal.PtrToStructure<KeyboardHookData>(lParam);
|
||||
|
||||
// Синтетический ввод присылают экранные клавиатуры, программы
|
||||
// автозамены и средства автоматизации: подменять их работу не наше дело
|
||||
// Synthetic input comes from on-screen keyboards, text expanders and
|
||||
// automation tools: overriding what they do is none of our business
|
||||
bool injected = (data.flags & LowLevelKeyHookFlagInjected) != 0;
|
||||
|
||||
if ((isKeyDown || isKeyUp) && !injected && _filter((int)data.vkCode, isKeyDown))
|
||||
{
|
||||
// Ненулевой результат вместо CallNextHookEx обрывает цепочку: событие
|
||||
// не дойдёт ни до приложения, ни до обработчика регистра в Windows
|
||||
// A non-zero result instead of CallNextHookEx breaks the chain: the event
|
||||
// will reach neither the application nor the Windows caps-lock handler
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Win32 API для окна-подсказки: стили, позиционирование у курсора
|
||||
/// и масштаб монитора, на котором курсор находится.
|
||||
/// Win32 API for the popup window: styles, positioning near the cursor
|
||||
/// and the scale of the monitor the cursor is on.
|
||||
/// </summary>
|
||||
internal static class PopupWindowNative
|
||||
{
|
||||
@@ -62,9 +62,9 @@ internal static class PopupWindowNative
|
||||
}
|
||||
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
// Окно не забирает фокус у активного приложения
|
||||
// The window does not take focus away from the active application
|
||||
private const int WS_EX_NOACTIVATE = 0x08000000;
|
||||
// И не попадает в Alt+Tab
|
||||
// And does not show up in Alt+Tab
|
||||
private const int WS_EX_TOOLWINDOW = 0x00000080;
|
||||
|
||||
private const uint SWP_NOSIZE = 0x0001;
|
||||
@@ -81,8 +81,8 @@ internal static class PopupWindowNative
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Подсказка всплывает поверх чужих приложений, поэтому она не должна
|
||||
/// ни активироваться сама, ни отбирать фокус ввода у активного окна.
|
||||
/// The popup shows up on top of other applications, so it must neither
|
||||
/// activate itself nor steal input focus from the active window.
|
||||
/// </summary>
|
||||
internal static void MakePassive(IntPtr hWnd)
|
||||
{
|
||||
@@ -91,10 +91,10 @@ internal static class PopupWindowNative
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Двигает окно в точку экрана, не меняя размер и порядок окон.
|
||||
/// Координаты — физические пиксели: у мониторов разный масштаб, а
|
||||
/// Window.Left/Top пересчитываются по DPI того монитора, где окно сейчас,
|
||||
/// и на соседнем мониторе дают промах.
|
||||
/// Moves the window to a screen point without changing its size or z-order.
|
||||
/// The coordinates are physical pixels: monitors have different scaling, while
|
||||
/// Window.Left/Top are converted using the DPI of the monitor the window is on
|
||||
/// right now, which misses the target on a neighbouring monitor.
|
||||
/// </summary>
|
||||
internal static void MoveTo(IntPtr hWnd, int x, int y)
|
||||
{
|
||||
@@ -102,26 +102,27 @@ internal static class PopupWindowNative
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Задаёт положение и размер окна в физических пикселях.
|
||||
/// Sets the window position and size in physical pixels.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Размер выставляется именно так, а не через Width/Height: при первом показе
|
||||
/// окно ещё подчиняется системному минимальному размеру окна (SM_CXMIN×SM_CYMIN)
|
||||
/// и подсказка выходит заметно крупнее текста. К моменту вызова окно уже
|
||||
/// показано и стало popup-окном, на которое это ограничение не действует.
|
||||
/// The size is set this way rather than through Width/Height: on the first show the
|
||||
/// window is still subject to the system minimum window size (SM_CXMIN×SM_CYMIN)
|
||||
/// and the popup comes out noticeably larger than its text. By the time this is
|
||||
/// called the window is already shown and has become a popup window, which that
|
||||
/// restriction does not apply to.
|
||||
/// </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>
|
||||
/// <summary>The scale of the monitor the point is on (1.0 at 96 DPI).</summary>
|
||||
internal static double GetScaleAt(Point point) =>
|
||||
GetScaleOf(MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST));
|
||||
|
||||
/// <summary>
|
||||
/// Рабочая область монитора с активным окном — без панели задач — и его масштаб.
|
||||
/// Именно на этом мониторе пользователь сейчас работает.
|
||||
/// The work area of the monitor holding the active window — without the taskbar —
|
||||
/// and its scale. That is the monitor the user is working on right now.
|
||||
/// </summary>
|
||||
internal static (Rect WorkArea, double Scale) GetActiveMonitorWorkArea()
|
||||
{
|
||||
|
||||
@@ -3,14 +3,14 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Win32 API для размещения окна настроек: его собственные границы и рабочая
|
||||
/// область монитора, на который окно просят поставить.
|
||||
/// Win32 API for placing the settings window: its own bounds and the work area
|
||||
/// of the monitor the window is asked to be put on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Границы берутся у системы, а не из <c>Window.Left/Top/Width/Height</c>:
|
||||
/// высота окна подстраивается под содержимое, и WPF пересчитывает эти свойства
|
||||
/// по DPI монитора, а рабочая область монитора приходит в пикселях. Считать
|
||||
/// центр в одних единицах проще, чем переводить туда-обратно.
|
||||
/// The bounds are taken from the system rather than from <c>Window.Left/Top/Width/Height</c>:
|
||||
/// the window height adapts to its content, and WPF converts those properties using the
|
||||
/// monitor DPI, while the monitor work area comes in pixels. Computing the centre in a
|
||||
/// single unit is simpler than converting back and forth.
|
||||
/// </remarks>
|
||||
internal static class WindowPlacementNative
|
||||
{
|
||||
@@ -34,13 +34,13 @@ internal static class WindowPlacementNative
|
||||
|
||||
private const uint MONITOR_DEFAULTTONEAREST = 2;
|
||||
|
||||
/// <summary>Границы окна в пикселях экрана — вместе с рамкой и заголовком.</summary>
|
||||
/// <summary>The window bounds in screen pixels — including the frame and the title bar.</summary>
|
||||
internal static PopupWindowNative.Rect? TryGetBounds(IntPtr hWnd) =>
|
||||
GetWindowRect(hWnd, out PopupWindowNative.Rect bounds) ? bounds : null;
|
||||
|
||||
/// <summary>
|
||||
/// Рабочая область — без панели задач — того монитора, на котором
|
||||
/// прямоугольник находится целиком или хотя бы большей частью.
|
||||
/// The work area — without the taskbar — of the monitor that holds the
|
||||
/// rectangle entirely, or at least most of it.
|
||||
/// </summary>
|
||||
internal static PopupWindowNative.Rect? TryGetWorkAreaNear(PopupWindowNative.Rect rect)
|
||||
{
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Оформление рамки окна средствами системы: заголовок рисует Windows,
|
||||
/// и в тёмной теме его нужно переключать отдельно от содержимого окна.
|
||||
/// Window frame styling by the system: the title bar is drawn by Windows,
|
||||
/// and in the dark theme it has to be switched separately from the window content.
|
||||
/// </summary>
|
||||
internal static class WindowThemeNative
|
||||
{
|
||||
@@ -14,8 +14,8 @@ internal static class WindowThemeNative
|
||||
private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Перекрашивает заголовок окна. На сборках Windows 10 до 2004 атрибут
|
||||
/// не поддерживается — заголовок просто останется светлым.
|
||||
/// Recolours the window title bar. On Windows 10 builds before 2004 the attribute
|
||||
/// is not supported — the title bar simply stays light.
|
||||
/// </summary>
|
||||
internal static void SetDarkTitleBar(IntPtr hWnd, bool isDark)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user