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: 2afe9976a2
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>
This commit is contained in:
Ayman Bagabas
2024-07-13 22:34:16 +00:00
committed by Alex Brainman
parent 0eac9b5475
commit bce4cf76d8
4 changed files with 101 additions and 2 deletions

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
)

View File

@@ -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 {