From bce4cf76d859904eacef4ad1c5fe647d794b0331 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Sat, 13 Jul 2024 22:34:16 +0000 Subject: [PATCH] windows: add GetKeyboardLayout & ToUnicodeEx These are used along with GetForegroundWindow and GetWindowThreadProcessId to determine the current user layout and translate the base key the user has pressed. Change-Id: Ib833ba7ab54213d83e889ff74c5bc0ace5edbe95 GitHub-Last-Rev: 2afe9976a2d1f6a8dda9bd74cf07b207246bfcc6 GitHub-Pull-Request: golang/sys#188 Reviewed-on: https://go-review.googlesource.com/c/sys/+/574755 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: Than McIntosh Reviewed-by: Alex Brainman Reviewed-by: Ayman Bagabas --- windows/syscall_windows.go | 12 +++++++-- windows/syscall_windows_test.go | 47 +++++++++++++++++++++++++++++++++ windows/types_windows.go | 11 ++++++++ windows/zsyscall_windows.go | 33 +++++++++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go index 6525c62f3..1fa34fd17 100644 --- a/windows/syscall_windows.go +++ b/windows/syscall_windows.go @@ -17,8 +17,10 @@ import ( "unsafe" ) -type Handle uintptr -type HWND uintptr +type ( + Handle uintptr + HWND uintptr +) const ( InvalidHandle = ^Handle(0) @@ -211,6 +213,10 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) (handle Handle, err error) //sys ShellExecute(hwnd Handle, verb *uint16, file *uint16, args *uint16, cwd *uint16, showCmd int32) (err error) [failretval<=32] = shell32.ShellExecuteW //sys GetWindowThreadProcessId(hwnd HWND, pid *uint32) (tid uint32, err error) = user32.GetWindowThreadProcessId +//sys LoadKeyboardLayout(name *uint16, flags uint32) (hkl Handle, err error) [failretval==0] = user32.LoadKeyboardLayoutW +//sys UnloadKeyboardLayout(hkl Handle) (err error) = user32.UnloadKeyboardLayout +//sys GetKeyboardLayout(tid uint32) (hkl Handle) = user32.GetKeyboardLayout +//sys ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) = user32.ToUnicodeEx //sys GetShellWindow() (shellWindow HWND) = user32.GetShellWindow //sys MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) [failretval==0] = user32.MessageBoxW //sys ExitWindowsEx(flags uint32, reason uint32) (err error) = user32.ExitWindowsEx @@ -1368,9 +1374,11 @@ func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) { func SetsockoptInet4Addr(fd Handle, level, opt int, value [4]byte) (err error) { return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&value[0])), 4) } + func SetsockoptIPMreq(fd Handle, level, opt int, mreq *IPMreq) (err error) { return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(mreq)), int32(unsafe.Sizeof(*mreq))) } + func SetsockoptIPv6Mreq(fd Handle, level, opt int, mreq *IPv6Mreq) (err error) { return syscall.EWINDOWS } diff --git a/windows/syscall_windows_test.go b/windows/syscall_windows_test.go index 770815411..ca8a70d2a 100644 --- a/windows/syscall_windows_test.go +++ b/windows/syscall_windows_test.go @@ -1437,3 +1437,50 @@ uintptr_t beep(void) { t.Fatal("LoadLibraryEx unexpectedly found beep.dll") } } + +func TestGetKeyboardLayout(t *testing.T) { + fg := windows.GetForegroundWindow() + tid, err := windows.GetWindowThreadProcessId(fg, nil) + if err != nil { + t.Fatalf("GetWindowThreadProcessId failed: %v", err) + } + + // We don't care about the result, just that it doesn't crash. + _ = windows.GetKeyboardLayout(tid) +} + +func TestToUnicodeEx(t *testing.T) { + var utf16Buf [16]uint16 + + // Arabic (101) Keyboard Identifier + // See https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values + ara, err := windows.UTF16PtrFromString("00000401") + if err != nil { + t.Fatalf("UTF16PtrFromString failed: %v", err) + } + araLayout, err := windows.LoadKeyboardLayout(ara, windows.KLF_ACTIVATE) + if err != nil { + t.Fatalf("LoadKeyboardLayout failed: %v", err) + } + + var keyState [256]byte + ret := windows.ToUnicodeEx( + 0x41, // 'A' vkCode + 0x1e, // 'A' scanCode + &keyState[0], + &utf16Buf[0], + int32(len(utf16Buf)), + 0x4, // don't change keyboard state + araLayout, + ) + + if ret != 1 { + t.Errorf("ToUnicodeEx failed, wanted 1, got %d", ret) + } + if utf16Buf[0] != 'ش' { + t.Errorf("ToUnicodeEx failed, wanted 'ش', got %q", utf16Buf[0]) + } + if err := windows.UnloadKeyboardLayout(araLayout); err != nil { + t.Errorf("UnloadKeyboardLayout failed: %v", err) + } +} diff --git a/windows/types_windows.go b/windows/types_windows.go index b8d93f307..4d0c15745 100644 --- a/windows/types_windows.go +++ b/windows/types_windows.go @@ -3418,3 +3418,14 @@ type DCB struct { EvtChar byte wReserved1 uint16 } + +// Keyboard Layout Flags. +// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadkeyboardlayoutw +const ( + KLF_ACTIVATE = 0x00000001 + KLF_SUBSTITUTE_OK = 0x00000002 + KLF_REORDER = 0x00000008 + KLF_REPLACELANG = 0x00000010 + KLF_NOTELLSHELL = 0x00000080 + KLF_SETFORPROCESS = 0x00000100 +) diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go index eba761018..92e3cf31c 100644 --- a/windows/zsyscall_windows.go +++ b/windows/zsyscall_windows.go @@ -478,12 +478,16 @@ var ( procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow") procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow") procGetGUIThreadInfo = moduser32.NewProc("GetGUIThreadInfo") + procGetKeyboardLayout = moduser32.NewProc("GetKeyboardLayout") procGetShellWindow = moduser32.NewProc("GetShellWindow") procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId") procIsWindow = moduser32.NewProc("IsWindow") procIsWindowUnicode = moduser32.NewProc("IsWindowUnicode") procIsWindowVisible = moduser32.NewProc("IsWindowVisible") + procLoadKeyboardLayoutW = moduser32.NewProc("LoadKeyboardLayoutW") procMessageBoxW = moduser32.NewProc("MessageBoxW") + procToUnicodeEx = moduser32.NewProc("ToUnicodeEx") + procUnloadKeyboardLayout = moduser32.NewProc("UnloadKeyboardLayout") procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock") procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock") procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW") @@ -4082,6 +4086,12 @@ func GetGUIThreadInfo(thread uint32, info *GUIThreadInfo) (err error) { return } +func GetKeyboardLayout(tid uint32) (hkl Handle) { + r0, _, _ := syscall.Syscall(procGetKeyboardLayout.Addr(), 1, uintptr(tid), 0, 0) + hkl = Handle(r0) + return +} + func GetShellWindow() (shellWindow HWND) { r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0) shellWindow = HWND(r0) @@ -4115,6 +4125,15 @@ func IsWindowVisible(hwnd HWND) (isVisible bool) { return } +func LoadKeyboardLayout(name *uint16, flags uint32) (hkl Handle, err error) { + r0, _, e1 := syscall.Syscall(procLoadKeyboardLayoutW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(flags), 0) + hkl = Handle(r0) + if hkl == 0 { + err = errnoErr(e1) + } + return +} + func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) { r0, _, e1 := syscall.Syscall6(procMessageBoxW.Addr(), 4, uintptr(hwnd), uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(caption)), uintptr(boxtype), 0, 0) ret = int32(r0) @@ -4124,6 +4143,20 @@ func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret i return } +func ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) { + r0, _, _ := syscall.Syscall9(procToUnicodeEx.Addr(), 7, uintptr(vkey), uintptr(scancode), uintptr(unsafe.Pointer(keystate)), uintptr(unsafe.Pointer(pwszBuff)), uintptr(cchBuff), uintptr(flags), uintptr(hkl), 0, 0) + ret = int32(r0) + return +} + +func UnloadKeyboardLayout(hkl Handle) (err error) { + r1, _, e1 := syscall.Syscall(procUnloadKeyboardLayout.Addr(), 1, uintptr(hkl), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) { var _p0 uint32 if inheritExisting {