fixed bug - careet in notepad and menu start OS
This commit is contained in:
@@ -15,29 +15,6 @@ namespace CursorLang.Interop;
|
||||
/// </remarks>
|
||||
internal static class CaretNative
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct GuiThreadInfo
|
||||
{
|
||||
public int cbSize;
|
||||
public uint flags;
|
||||
public IntPtr hwndActive;
|
||||
public IntPtr hwndFocus;
|
||||
public IntPtr hwndCapture;
|
||||
public IntPtr hwndMenuOwner;
|
||||
public IntPtr hwndMoveSize;
|
||||
public IntPtr hwndCaret;
|
||||
public PopupWindowNative.Rect rcCaret;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetGUIThreadInfo(uint idThread, ref GuiThreadInfo lpgui);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ClientToScreen(IntPtr hWnd, ref PopupWindowNative.Point lpPoint);
|
||||
|
||||
@@ -45,6 +22,12 @@ internal static class CaretNative
|
||||
private static extern int AccessibleObjectFromWindow(IntPtr hwnd, uint dwObjectId,
|
||||
ref Guid riid, out IAccessible ppvObject);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out PopupWindowNative.Rect lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetDpiForWindow(IntPtr hWnd);
|
||||
|
||||
private const uint OBJID_CARET = 0xFFFFFFF8;
|
||||
private const int CHILDID_SELF = 0;
|
||||
|
||||
@@ -54,18 +37,55 @@ internal static class CaretNative
|
||||
/// </summary>
|
||||
internal static PopupWindowNative.Rect? TryGetCaretRect()
|
||||
{
|
||||
var info = new GuiThreadInfo { cbSize = Marshal.SizeOf<GuiThreadInfo>() };
|
||||
uint threadId = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
|
||||
if (!GetGUIThreadInfo(threadId, ref info))
|
||||
if (!ForegroundInputNative.TryGetInfo(out ForegroundInputNative.GuiThreadInfo info))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return TryGetSystemCaret(info)
|
||||
PopupWindowNative.Rect? caret = TryGetSystemCaret(info)
|
||||
?? TryGetAccessibleCaret(info.hwndFocus)
|
||||
?? TryGetAutomationCaret();
|
||||
|
||||
return caret is null ? null : Validate(caret.Value, info.hwndFocus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отсеивает заведомо неверные координаты.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Часть приложений отдаёт положение каретки в своей системе координат либо
|
||||
/// без учёта масштаба экрана, и подсказка уезжает далеко от поля ввода.
|
||||
/// Каретка обязана находиться внутри окна ввода — это и проверяем, а перед
|
||||
/// отказом пробуем истолковать координаты как немасштабированные.
|
||||
/// </remarks>
|
||||
private static PopupWindowNative.Rect? Validate(PopupWindowNative.Rect caret, IntPtr hwndFocus)
|
||||
{
|
||||
if (hwndFocus == IntPtr.Zero || !GetWindowRect(hwndFocus, out PopupWindowNative.Rect window))
|
||||
{
|
||||
return caret;
|
||||
}
|
||||
|
||||
if (IsInside(caret, window))
|
||||
{
|
||||
return caret;
|
||||
}
|
||||
|
||||
double scale = GetDpiForWindow(hwndFocus) / 96.0;
|
||||
var scaled = new PopupWindowNative.Rect
|
||||
{
|
||||
Left = (int)(caret.Left * scale),
|
||||
Top = (int)(caret.Top * scale),
|
||||
Right = (int)(caret.Right * scale),
|
||||
Bottom = (int)(caret.Bottom * scale),
|
||||
};
|
||||
|
||||
return IsInside(scaled, window) ? scaled : null;
|
||||
}
|
||||
|
||||
private static bool IsInside(PopupWindowNative.Rect inner, PopupWindowNative.Rect outer) =>
|
||||
inner.Left >= outer.Left && inner.Right <= outer.Right &&
|
||||
inner.Top >= outer.Top && inner.Bottom <= outer.Bottom;
|
||||
|
||||
/// <summary>Сколько ждём ответа от чужого приложения по UI Automation.</summary>
|
||||
private static readonly TimeSpan AutomationTimeout = TimeSpan.FromMilliseconds(150);
|
||||
|
||||
@@ -127,7 +147,7 @@ internal static class CaretNative
|
||||
}
|
||||
|
||||
// Системная каретка: координаты приходят относительно окна, которому она принадлежит
|
||||
private static PopupWindowNative.Rect? TryGetSystemCaret(GuiThreadInfo info)
|
||||
private static PopupWindowNative.Rect? TryGetSystemCaret(ForegroundInputNative.GuiThreadInfo info)
|
||||
{
|
||||
if (info.hwndCaret == IntPtr.Zero || IsEmpty(info.rcCaret))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Сведения о вводе в активном приложении: какое окно держит фокус клавиатуры
|
||||
/// и где находится каретка.
|
||||
/// </summary>
|
||||
internal static class ForegroundInputNative
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct GuiThreadInfo
|
||||
{
|
||||
public int cbSize;
|
||||
public uint flags;
|
||||
public IntPtr hwndActive;
|
||||
public IntPtr hwndFocus;
|
||||
public IntPtr hwndCapture;
|
||||
public IntPtr hwndMenuOwner;
|
||||
public IntPtr hwndMoveSize;
|
||||
public IntPtr hwndCaret;
|
||||
public PopupWindowNative.Rect rcCaret;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetGUIThreadInfo(uint idThread, ref GuiThreadInfo lpgui);
|
||||
|
||||
/// <summary>
|
||||
/// Состояние ввода потока переднего плана.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Нулевой идентификатор потока не случаен: у современных приложений окно
|
||||
/// верхнего уровня и окно ввода живут в разных потоках, и спрашивать нужно
|
||||
/// именно про передний план целиком.
|
||||
/// </remarks>
|
||||
internal static bool TryGetInfo(out GuiThreadInfo info)
|
||||
{
|
||||
info = new GuiThreadInfo { cbSize = Marshal.SizeOf<GuiThreadInfo>() };
|
||||
return GetGUIThreadInfo(0, ref info);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
||||
namespace CursorLang.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Win32 API для определения раскладки активного окна.
|
||||
/// Win32 API для определения раскладки активного приложения.
|
||||
/// </summary>
|
||||
internal static class KeyboardLayoutNative
|
||||
{
|
||||
@@ -16,6 +16,26 @@ internal static class KeyboardLayoutNative
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetKeyboardLayout(uint idThread);
|
||||
|
||||
/// <summary>
|
||||
/// Раскладка, которой сейчас печатает пользователь.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Спрашиваем окно с фокусом клавиатуры, а не окно верхнего плана: у Блокнота
|
||||
/// Windows 11, меню «Пуск» и прочих приложений на WinUI поле ввода живёт в
|
||||
/// отдельном потоке, и раскладка меняется только у него. У потока главного
|
||||
/// окна она остаётся прежней, и переключение проходит незамеченным.
|
||||
/// </remarks>
|
||||
internal static int GetActiveLocaleId()
|
||||
{
|
||||
if (ForegroundInputNative.TryGetInfo(out ForegroundInputNative.GuiThreadInfo info) &&
|
||||
info.hwndFocus != IntPtr.Zero)
|
||||
{
|
||||
return GetLocaleIdOf(info.hwndFocus);
|
||||
}
|
||||
|
||||
return GetLocaleIdOf(GetForegroundWindow());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Идентификатор локали для окна. Раскладка в Windows привязана к потоку,
|
||||
/// поэтому так её видно у любого приложения, а не только у своего.
|
||||
|
||||
@@ -37,13 +37,12 @@ public sealed class KeyboardLayoutService : IKeyboardLayoutService, IDisposable
|
||||
|
||||
public event EventHandler<LayoutChangedEventArgs>? LayoutChanged;
|
||||
|
||||
public KeyboardLayout Current =>
|
||||
KeyboardLayout.FromLocaleId(KeyboardLayoutNative.GetLocaleIdOf(KeyboardLayoutNative.GetForegroundWindow()));
|
||||
public KeyboardLayout Current => KeyboardLayout.FromLocaleId(KeyboardLayoutNative.GetActiveLocaleId());
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_lastForegroundWindow = KeyboardLayoutNative.GetForegroundWindow();
|
||||
_lastLocaleId = KeyboardLayoutNative.GetLocaleIdOf(_lastForegroundWindow);
|
||||
_lastLocaleId = KeyboardLayoutNative.GetActiveLocaleId();
|
||||
_pollTimer.Start();
|
||||
}
|
||||
|
||||
@@ -66,7 +65,7 @@ public sealed class KeyboardLayoutService : IKeyboardLayoutService, IDisposable
|
||||
bool appSwitched = foreground != _lastForegroundWindow;
|
||||
_lastForegroundWindow = foreground;
|
||||
|
||||
int localeId = KeyboardLayoutNative.GetLocaleIdOf(foreground);
|
||||
int localeId = KeyboardLayoutNative.GetActiveLocaleId();
|
||||
if (localeId == _lastLocaleId)
|
||||
{
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user