86 lines
3.1 KiB
C#
86 lines
3.1 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace CursorLang.Interop;
|
|
|
|
/// <summary>
|
|
/// Win32 API for reading the layout of the active application.
|
|
/// </summary>
|
|
internal static class KeyboardLayoutNative
|
|
{
|
|
private const uint WmInputLangChangeRequest = 0x0050;
|
|
|
|
/// <summary>Take the next layout from the system list.</summary>
|
|
private static readonly IntPtr InputLangChangeForward = new(0x0002);
|
|
|
|
/// <summary>HKL_NEXT — the same request in the language of older Windows versions.</summary>
|
|
private static readonly IntPtr HklNext = new(1);
|
|
|
|
[DllImport("user32.dll")]
|
|
internal static extern IntPtr GetForegroundWindow();
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern IntPtr GetKeyboardLayout(uint idThread);
|
|
|
|
[DllImport("user32.dll")]
|
|
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>
|
|
/// 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()
|
|
{
|
|
IntPtr target = GetInputWindow();
|
|
if (target == IntPtr.Zero)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 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>
|
|
/// 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()
|
|
{
|
|
if (ForegroundInputNative.TryGetInfo(out ForegroundInputNative.GuiThreadInfo info) &&
|
|
info.hwndFocus != IntPtr.Zero)
|
|
{
|
|
return info.hwndFocus;
|
|
}
|
|
|
|
return GetForegroundWindow();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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)
|
|
{
|
|
uint threadId = GetWindowThreadProcessId(hWnd, IntPtr.Zero);
|
|
return (int)GetKeyboardLayout(threadId).ToInt64() & 0xFFFF;
|
|
}
|
|
}
|