286 lines
5.2 KiB
Go
286 lines
5.2 KiB
Go
|
package keyboard
|
||
|
|
||
|
import (
|
||
|
"syscall"
|
||
|
"unsafe"
|
||
|
|
||
|
"golang.org/x/sys/windows"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
vk_backspace = 0x8
|
||
|
vk_tab = 0x9
|
||
|
vk_enter = 0xd
|
||
|
vk_esc = 0x1b
|
||
|
vk_space = 0x20
|
||
|
vk_pgup = 0x21
|
||
|
vk_pgdn = 0x22
|
||
|
vk_end = 0x23
|
||
|
vk_home = 0x24
|
||
|
vk_arrow_left = 0x25
|
||
|
vk_arrow_up = 0x26
|
||
|
vk_arrow_right = 0x27
|
||
|
vk_arrow_down = 0x28
|
||
|
vk_insert = 0x2d
|
||
|
vk_delete = 0x2e
|
||
|
|
||
|
vk_f1 = 0x70
|
||
|
vk_f2 = 0x71
|
||
|
vk_f3 = 0x72
|
||
|
vk_f4 = 0x73
|
||
|
vk_f5 = 0x74
|
||
|
vk_f6 = 0x75
|
||
|
vk_f7 = 0x76
|
||
|
vk_f8 = 0x77
|
||
|
vk_f9 = 0x78
|
||
|
vk_f10 = 0x79
|
||
|
vk_f11 = 0x7a
|
||
|
vk_f12 = 0x7b
|
||
|
|
||
|
right_alt_pressed = 0x1
|
||
|
left_alt_pressed = 0x2
|
||
|
right_ctrl_pressed = 0x4
|
||
|
left_ctrl_pressed = 0x8
|
||
|
shift_pressed = 0x10
|
||
|
|
||
|
k32_keyEvent = 0x1
|
||
|
)
|
||
|
|
||
|
type (
|
||
|
wchar uint16
|
||
|
dword uint32
|
||
|
word uint16
|
||
|
|
||
|
k32_event struct {
|
||
|
key_down int32
|
||
|
repeat_count word
|
||
|
virtual_key_code word
|
||
|
virtual_scan_code word
|
||
|
unicode_char wchar
|
||
|
control_key_state dword
|
||
|
}
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||
|
|
||
|
k32_WaitForMultipleObjects = kernel32.NewProc("WaitForMultipleObjects")
|
||
|
k32_ReadConsoleInputW = kernel32.NewProc("ReadConsoleInputW")
|
||
|
|
||
|
hConsoleIn syscall.Handle
|
||
|
hInterrupt windows.Handle
|
||
|
|
||
|
quit = make(chan bool)
|
||
|
|
||
|
// This is just to prevent heap allocs at all costs
|
||
|
tmpArg dword
|
||
|
)
|
||
|
|
||
|
func getError(errno syscall.Errno) error {
|
||
|
if errno != 0 {
|
||
|
return error(errno)
|
||
|
} else {
|
||
|
return syscall.EINVAL
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getKeyEvent(r *k32_event) (KeyEvent, bool) {
|
||
|
e := KeyEvent{}
|
||
|
|
||
|
if r.key_down == 0 {
|
||
|
return e, false
|
||
|
}
|
||
|
|
||
|
ctrlPressed := r.control_key_state&(left_ctrl_pressed|right_ctrl_pressed) != 0
|
||
|
|
||
|
if r.virtual_key_code >= vk_f1 && r.virtual_key_code <= vk_f12 {
|
||
|
switch r.virtual_key_code {
|
||
|
case vk_f1:
|
||
|
e.Key = KeyF1
|
||
|
case vk_f2:
|
||
|
e.Key = KeyF2
|
||
|
case vk_f3:
|
||
|
e.Key = KeyF3
|
||
|
case vk_f4:
|
||
|
e.Key = KeyF4
|
||
|
case vk_f5:
|
||
|
e.Key = KeyF5
|
||
|
case vk_f6:
|
||
|
e.Key = KeyF6
|
||
|
case vk_f7:
|
||
|
e.Key = KeyF7
|
||
|
case vk_f8:
|
||
|
e.Key = KeyF8
|
||
|
case vk_f9:
|
||
|
e.Key = KeyF9
|
||
|
case vk_f10:
|
||
|
e.Key = KeyF10
|
||
|
case vk_f11:
|
||
|
e.Key = KeyF11
|
||
|
case vk_f12:
|
||
|
e.Key = KeyF12
|
||
|
default:
|
||
|
panic("unreachable")
|
||
|
}
|
||
|
|
||
|
return e, true
|
||
|
}
|
||
|
|
||
|
if r.virtual_key_code <= vk_delete {
|
||
|
switch r.virtual_key_code {
|
||
|
case vk_insert:
|
||
|
e.Key = KeyInsert
|
||
|
case vk_delete:
|
||
|
e.Key = KeyDelete
|
||
|
case vk_home:
|
||
|
e.Key = KeyHome
|
||
|
case vk_end:
|
||
|
e.Key = KeyEnd
|
||
|
case vk_pgup:
|
||
|
e.Key = KeyPgup
|
||
|
case vk_pgdn:
|
||
|
e.Key = KeyPgdn
|
||
|
case vk_arrow_up:
|
||
|
e.Key = KeyArrowUp
|
||
|
case vk_arrow_down:
|
||
|
e.Key = KeyArrowDown
|
||
|
case vk_arrow_left:
|
||
|
e.Key = KeyArrowLeft
|
||
|
case vk_arrow_right:
|
||
|
e.Key = KeyArrowRight
|
||
|
case vk_backspace:
|
||
|
if ctrlPressed {
|
||
|
e.Key = KeyBackspace2
|
||
|
} else {
|
||
|
e.Key = KeyBackspace
|
||
|
}
|
||
|
case vk_tab:
|
||
|
e.Key = KeyTab
|
||
|
case vk_enter:
|
||
|
e.Key = KeyEnter
|
||
|
case vk_esc:
|
||
|
e.Key = KeyEsc
|
||
|
case vk_space:
|
||
|
if ctrlPressed {
|
||
|
// manual return here, because KeyCtrlSpace is zero
|
||
|
e.Key = KeyCtrlSpace
|
||
|
return e, true
|
||
|
} else {
|
||
|
e.Key = KeySpace
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if e.Key != 0 {
|
||
|
return e, true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ctrlPressed {
|
||
|
if Key(r.unicode_char) >= KeyCtrlA && Key(r.unicode_char) <= KeyCtrlRsqBracket {
|
||
|
e.Key = Key(r.unicode_char)
|
||
|
return e, true
|
||
|
}
|
||
|
switch r.virtual_key_code {
|
||
|
case 192, 50:
|
||
|
// manual return here, because KeyCtrl2 is zero
|
||
|
e.Key = KeyCtrl2
|
||
|
return e, true
|
||
|
case 51:
|
||
|
e.Key = KeyCtrl3
|
||
|
case 52:
|
||
|
e.Key = KeyCtrl4
|
||
|
case 53:
|
||
|
e.Key = KeyCtrl5
|
||
|
case 54:
|
||
|
e.Key = KeyCtrl6
|
||
|
case 189, 191, 55:
|
||
|
e.Key = KeyCtrl7
|
||
|
case 8, 56:
|
||
|
e.Key = KeyCtrl8
|
||
|
}
|
||
|
|
||
|
if e.Key != 0 {
|
||
|
return e, true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if r.unicode_char != 0 {
|
||
|
e.Rune = rune(r.unicode_char)
|
||
|
return e, true
|
||
|
}
|
||
|
|
||
|
return e, false
|
||
|
}
|
||
|
|
||
|
func produceEvent(event KeyEvent) bool {
|
||
|
select {
|
||
|
case <-quit:
|
||
|
return false
|
||
|
case inputComm <- event:
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func inputEventsProducer() {
|
||
|
var input [20]uint16
|
||
|
for {
|
||
|
// Wait for events
|
||
|
// https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects
|
||
|
r0, _, e1 := syscall.Syscall6(k32_WaitForMultipleObjects.Addr(), 4,
|
||
|
uintptr(2), uintptr(unsafe.Pointer(&hConsoleIn)), 0, windows.INFINITE, 0, 0)
|
||
|
if uint32(r0) == windows.WAIT_FAILED && false == produceEvent(KeyEvent{Err: getError(e1)}) {
|
||
|
return
|
||
|
}
|
||
|
select {
|
||
|
case <-quit:
|
||
|
return
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
// Get console input
|
||
|
r0, _, e1 = syscall.Syscall6(k32_ReadConsoleInputW.Addr(), 4,
|
||
|
uintptr(hConsoleIn), uintptr(unsafe.Pointer(&input[0])), 1, uintptr(unsafe.Pointer(&tmpArg)), 0, 0)
|
||
|
if int(r0) == 0 {
|
||
|
if false == produceEvent(KeyEvent{Err: getError(e1)}) {
|
||
|
return
|
||
|
}
|
||
|
} else if input[0] == k32_keyEvent {
|
||
|
kEvent := (*k32_event)(unsafe.Pointer(&input[2]))
|
||
|
ev, ok := getKeyEvent(kEvent)
|
||
|
if ok {
|
||
|
for i := 0; i < int(kEvent.repeat_count); i++ {
|
||
|
if false == produceEvent(ev) {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func initConsole() (err error) {
|
||
|
// Create an interrupt event
|
||
|
hInterrupt, err = windows.CreateEvent(nil, 0, 0, nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
hConsoleIn, err = syscall.Open("CONIN$", windows.O_RDWR, 0)
|
||
|
if err != nil {
|
||
|
windows.Close(hInterrupt)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
go inputEventsProducer()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func releaseConsole() {
|
||
|
// Stop events producer
|
||
|
windows.SetEvent(hInterrupt)
|
||
|
quit <- true
|
||
|
|
||
|
syscall.Close(hConsoleIn)
|
||
|
windows.Close(hInterrupt)
|
||
|
}
|