using System.Runtime.InteropServices; namespace CursorLang.Interop; /// /// Win32 API для определения раскладки активного приложения. /// internal static class KeyboardLayoutNative { private const uint WmInputLangChangeRequest = 0x0050; /// Взять следующую раскладку из системного списка. private static readonly IntPtr InputLangChangeForward = new(0x0002); /// HKL_NEXT — та же просьба на языке старых версий Windows. 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); /// /// Раскладка, которой сейчас печатает пользователь. /// internal static int GetActiveLocaleId() => GetLocaleIdOf(GetInputWindow()); /// /// Просит активное приложение перейти на следующую раскладку из системного списка. /// /// /// Синтезировать системное сочетание вроде Alt+Shift не годится: его назначение /// пользователь меняет в настройках Windows и может отключить вовсе. Просьба /// сообщением от таких настроек не зависит и работает в чужом процессе. /// internal static void RequestNextLayout() { IntPtr target = GetInputWindow(); if (target == IntPtr.Zero) { return; } // Оба параметра означают одно и то же: разные версии Windows и разные // библиотеки интерфейса смотрят то на флаг, то на lParam PostMessage(target, WmInputLangChangeRequest, InputLangChangeForward, HklNext); } /// /// Окно, которому принадлежит ввод с клавиатуры. /// /// /// Спрашиваем окно с фокусом клавиатуры, а не окно верхнего плана: у Блокнота /// Windows 11, меню «Пуск» и прочих приложений на WinUI поле ввода живёт в /// отдельном потоке, и раскладка меняется только у него. У потока главного /// окна она остаётся прежней, и переключение проходит незамеченным. /// private static IntPtr GetInputWindow() { if (ForegroundInputNative.TryGetInfo(out ForegroundInputNative.GuiThreadInfo info) && info.hwndFocus != IntPtr.Zero) { return info.hwndFocus; } return GetForegroundWindow(); } /// /// Идентификатор локали для окна. Раскладка в Windows привязана к потоку, /// поэтому так её видно у любого приложения, а не только у своего. /// internal static int GetLocaleIdOf(IntPtr hWnd) { uint threadId = GetWindowThreadProcessId(hWnd, IntPtr.Zero); return (int)GetKeyboardLayout(threadId).ToInt64() & 0xFFFF; } }