using System.Runtime.InteropServices; namespace CursorLang.Core.Interop; /// /// Win32 API for reading the layout of the active application. /// internal static class KeyboardLayoutNative { private const uint WmInputLangChangeRequest = 0x0050; /// Take the next layout from the system list. private static readonly IntPtr InputLangChangeForward = new(0x0002); /// HKL_NEXT — the same request in the language of older Windows versions. 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); /// /// The layout the user is currently typing with. /// internal static int GetActiveLocaleId() => GetLocaleIdOf(GetInputWindow()); /// /// Asks the active application to switch to the next layout from the system list. /// /// /// 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. /// 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); } /// /// The window that owns keyboard input. /// /// /// 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. /// private static IntPtr GetInputWindow() { if (ForegroundInputNative.TryGetInfo(out ForegroundInputNative.GuiThreadInfo info) && info.hwndFocus != IntPtr.Zero) { return info.hwndFocus; } return GetForegroundWindow(); } /// /// 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. /// internal static int GetLocaleIdOf(IntPtr hWnd) { uint threadId = GetWindowThreadProcessId(hWnd, IntPtr.Zero); return (int)GetKeyboardLayout(threadId).ToInt64() & 0xFFFF; } }