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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Ayman Bagabas <ayman.bagabas@gmail.com>
diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go
index 6525c62..1fa34fd 100644
--- a/windows/syscall_windows.go
+++ b/windows/syscall_windows.go
@@ -17,8 +17,10 @@
 	"unsafe"
 )
 
-type Handle uintptr
-type HWND uintptr
+type (
+	Handle uintptr
+	HWND   uintptr
+)
 
 const (
 	InvalidHandle = ^Handle(0)
@@ -211,6 +213,10 @@
 //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 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 7708154..ca8a70d 100644
--- a/windows/syscall_windows_test.go
+++ b/windows/syscall_windows_test.go
@@ -1437,3 +1437,50 @@
 		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 b8d93f3..4d0c157 100644
--- a/windows/types_windows.go
+++ b/windows/types_windows.go
@@ -3418,3 +3418,14 @@
 	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 eba7610..92e3cf3 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -478,12 +478,16 @@
 	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 @@
 	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 @@
 	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 @@
 	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 {