Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions src/ManagedShell.Common/Helpers/KeyboardLayoutHelper.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,51 @@
using ManagedShell.Common.Structs;
using ManagedShell.Interop;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using static ManagedShell.Interop.NativeMethods;

namespace ManagedShell.Common.Helpers
{
public static class KeyboardLayoutHelper
{
private static uint GetFocusedThreadId()
{
var gti = new GUITHREADINFO();
gti.cbSize = Marshal.SizeOf(typeof(GUITHREADINFO));

// Some apps (e.g. Electron/WebView2 apps such as Teams, and WinUI3 apps such as the
// modern Notepad) keep their real keyboard focus on a child window that belongs to a
// different thread than the top-level foreground window. GetGUIThreadInfo(0, ...)
// resolves the true focused window first.
// idThread 0 asks for the foreground thread's queue; hwndFocus within it is the
// window that truly owns keyboard focus, which may belong to a different thread
// than the top-level foreground window itself.
if (GetGUIThreadInfo(0, ref gti))
{
IntPtr focusedWindow = gti.hwndFocus != IntPtr.Zero ? gti.hwndFocus : gti.hwndActive;

if (focusedWindow != IntPtr.Zero)
{
uint focusedThreadId = GetWindowThreadProcessId(focusedWindow, out _);

if (focusedThreadId != 0)
{
return focusedThreadId;
}
}
}

return GetWindowThreadProcessId(GetForegroundWindow(), out _);
}

public static KeyboardLayout GetKeyboardLayout(bool currentThread = false)
{
uint threadId = 0;
if (!currentThread)
threadId = NativeMethods.GetWindowThreadProcessId(NativeMethods.GetForegroundWindow(), out _);
threadId = GetFocusedThreadId();
var layout = NativeMethods.GetKeyboardLayout(threadId);

return new KeyboardLayout()
Expand All @@ -39,10 +72,10 @@ public static List<KeyboardLayout> GetKeyboardLayoutList()

public static bool SetKeyboardLayout(int layoutId)
{
return NativeMethods.PostMessage(0xffff,
(uint) NativeMethods.WM.INPUTLANGCHANGEREQUEST,
return PostMessage(0xffff,
(uint) WM.INPUTLANGCHANGEREQUEST,
0,
(long)NativeMethods.LoadKeyboardLayout(layoutId.ToString("x8"), (uint)(NativeMethods.KLF.SUBSTITUTE_OK | NativeMethods.KLF.ACTIVATE)));
(long)LoadKeyboardLayout(layoutId.ToString("x8"), (uint)(KLF.SUBSTITUTE_OK | KLF.ACTIVATE)));
}
}
}
17 changes: 17 additions & 0 deletions src/ManagedShell.Interop/NativeMethods.User32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3857,5 +3857,22 @@ public struct DEV_BROADCAST_HANDLE

[DllImport(User32_DllName)]
public static extern bool UnregisterDeviceNotification(IntPtr handle);

[StructLayout(LayoutKind.Sequential)]
public 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 Rect rcCaret;
}

[DllImport(User32_DllName, SetLastError = true)]
public static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui);
}
}