123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- /*
- * Public Domain Software
- *
- * I (Matthias Ladkau) am the author of the source code in this file.
- * I have placed the source code in this file in the public domain.
- *
- * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
- */
- package getch
- /*
- The code in this file was inspired by:
- http://code.google.com/p/termbox
- Pure Go termbox implementation
- */
- import (
- "sync"
- "syscall"
- "time"
- "unsafe"
- )
- /*
- attachReader attaches a character reader on Windows.
- */
- func attachReader() (getch, error) {
- // Create an event object which can receive input events
- eobj, err := createEvent()
- if err == nil {
- gw := &getchWindows{eobj, 0, 0, make(chan *internalKeyEvent), make(chan bool),
- &sync.WaitGroup{}}
- // Open the console input device
- if gw.in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0); err == nil {
- if gw.out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0); err == nil {
- // All is well we can start listening for events
- go gw.eventListener()
- // Make sure the input buffer is clear
- time.Sleep(100 * time.Millisecond)
- for gw.GetKeyAsync() != nil {
- time.Sleep(100 * time.Millisecond)
- }
- return gw, nil
- }
- }
- gw.Close()
- }
- return nil, err
- }
- // Getch object implementation for windows
- // =======================================
- /*
- getchWindows is the Windows based getch implementation.
- */
- type getchWindows struct {
- eobj syscall.Handle // Event object handle
- in syscall.Handle // Console input device handler
- out syscall.Handle // Console output device handle
- keyEventChan chan *internalKeyEvent // Channel for input events
- stopChan chan bool // Channel for shutdown event
- wg *sync.WaitGroup // Waitgroup for shutdown process
- }
- /*
- CursorPosition returns the current cursor position.
- */
- func (gw *getchWindows) CursorPosition() (int, int, error) {
- var x, y int
- var err error
- info, err := getConsoleScreenBufferInfo(gw.out)
- if err == nil {
- x = int(info.cursorPosition.x)
- y = int(info.cursorPosition.y)
- }
- return x, y, err
- }
- /*
- SetCursorPosition sets the current cursor position
- */
- func (gw *getchWindows) SetCursorPosition(x, y int) error {
- return setConsoleCursorPosition(gw.out, &winCOORD{int16(x), int16(y)})
- }
- /*
- GetKey returns the next key event or an error. This function blocks if no key
- event is available.
- */
- func (gw *getchWindows) GetKey() *internalKeyEvent {
- return <-gw.keyEventChan
- }
- /*
- GetKeyAsync returns the next key event or an error. This function does not block if no key
- event is available - in this case nil is returned.
- */
- func (gw *getchWindows) GetKeyAsync() *internalKeyEvent {
- select {
- case e := <-gw.keyEventChan:
- return e
- default:
- }
- return nil
- }
- /*
- Close detaches the character reader.
- */
- func (gw *getchWindows) Close() {
- // Send stop command and wait for the eventListener thread to end
- gw.wg.Add(1)
- // Empty all pending keys
- for gw.GetKeyAsync() != nil {
- time.Sleep(10 * time.Millisecond)
- }
- gw.stopChan <- true
- gw.wg.Wait()
- // Ignoring errors here since we are closing
- syscall.Close(gw.in)
- syscall.Close(gw.out)
- syscall.Close(gw.eobj)
- }
- /*
- eventListener is a thread which will listen for events.
- */
- func (gw *getchWindows) eventListener() {
- var ir inputRecord
- // Loop until an exit is requested
- loop:
- for true {
- ok, err := waitForMultipleObjects([]syscall.Handle{gw.in, gw.eobj})
- if err != nil {
- gw.keyEventChan <- &internalKeyEvent{err: err}
- }
- // Check if we should stop
- select {
- case <-gw.stopChan:
- break loop
- default:
- }
- if err == nil && ok {
- if err = readConsoleInput(gw.in, &ir); err != nil {
- gw.keyEventChan <- &internalKeyEvent{err: err}
- } else {
- // Only analyse actual keyboard events
- if ir.eventType == 0x1 {
- if kr := (*winKeyEvent)(unsafe.Pointer(&ir.event)); kr.keyDown == 1 {
- gw.keyEventChan <- gw.buildKeyEvent(kr)
- }
- }
- }
- }
- }
- gw.wg.Done()
- }
- /*
- buildKeyEvent builds an internalKeyEvent from a windows key event.
- */
- func (gw *getchWindows) buildKeyEvent(wke *winKeyEvent) *internalKeyEvent {
- ike := &internalKeyEvent{&KeyEvent{}, nil}
- ike.Alt = wke.controlKeyState&(0x0001|0x0002) != 0 // Check if right alt or left alt is pressed
- ike.Ctrl = wke.controlKeyState&(0x0004|0x0008) != 0 // Check if right ctrl or left ctrl is pressed
- ike.Shift = wke.controlKeyState&0x0010 != 0 // Check if shift is pressed
- ike.Rune = rune(wke.unicodeChar)
- // Check for printable character
- switch wke.virtualKeyCode {
- case 0x41: // Letters
- ike.Code = KeyA
- case 0x42:
- ike.Code = KeyB
- case 0x43:
- ike.Code = KeyC
- case 0x44:
- ike.Code = KeyD
- case 0x45:
- ike.Code = KeyE
- case 0x46:
- ike.Code = KeyF
- case 0x47:
- ike.Code = KeyG
- case 0x48:
- ike.Code = KeyH
- case 0x49:
- ike.Code = KeyI
- case 0x4a:
- ike.Code = KeyJ
- case 0x4b:
- ike.Code = KeyK
- case 0x4c:
- ike.Code = KeyL
- case 0x4d:
- ike.Code = KeyM
- case 0x4e:
- ike.Code = KeyN
- case 0x4f:
- ike.Code = KeyO
- case 0x50:
- ike.Code = KeyP
- case 0x51:
- ike.Code = KeyQ
- case 0x52:
- ike.Code = KeyR
- case 0x53:
- ike.Code = KeyS
- case 0x54:
- ike.Code = KeyT
- case 0x55:
- ike.Code = KeyU
- case 0x56:
- ike.Code = KeyV
- case 0x57:
- ike.Code = KeyW
- case 0x58:
- ike.Code = KeyX
- case 0x59:
- ike.Code = KeyY
- case 0x5a:
- ike.Code = KeyZ
- case 0x30: // Numbers
- ike.Code = Key0
- case 0x31:
- ike.Code = Key1
- case 0x32:
- ike.Code = Key2
- case 0x33:
- ike.Code = Key3
- case 0x34:
- ike.Code = Key4
- case 0x35:
- ike.Code = Key5
- case 0x36:
- ike.Code = Key6
- case 0x37:
- ike.Code = Key7
- case 0x38:
- ike.Code = Key8
- case 0x39:
- ike.Code = Key9
- case 0xdf: // Symbols
- ike.Code = KeyBacktick
- case 0xbd:
- ike.Code = KeyMinus
- case 0xbb:
- ike.Code = KeyEqual
- case 0xdc:
- ike.Code = KeyBackslash
- case 0xbc:
- ike.Code = KeyComma
- case 0xbe:
- ike.Code = KeyDot
- case 0xbf:
- ike.Code = KeySlash
- case 0xba:
- ike.Code = KeySemiColon
- case 0xc0:
- ike.Code = KeyQuote
- case 0xde:
- ike.Code = KeyHash
- case 0xdb:
- ike.Code = KeyBracketOpen
- case 0xdd:
- ike.Code = KeyBracketClose
- default:
- // Key pressed cannot be a printable character
- ike.Rune = 0x00
- }
- // Check for non-printable keys
- switch wke.virtualKeyCode {
- case 0x70:
- ike.Code = KeyF1
- case 0x71:
- ike.Code = KeyF2
- case 0x72:
- ike.Code = KeyF3
- case 0x73:
- ike.Code = KeyF4
- case 0x74:
- ike.Code = KeyF5
- case 0x75:
- ike.Code = KeyF6
- case 0x76:
- ike.Code = KeyF7
- case 0x77:
- ike.Code = KeyF8
- case 0x78:
- ike.Code = KeyF9
- case 0x79:
- ike.Code = KeyF10
- case 0x7a:
- ike.Code = KeyF11
- case 0x7b:
- ike.Code = KeyF12
- case 0x9:
- ike.Code = KeyTab
- case 0xd:
- ike.Code = KeyEnter
- case 0x8:
- ike.Code = KeyBackspace
- case 0x1b:
- ike.Code = KeyEsc
- case 0x2d:
- ike.Code = KeyInsert
- case 0x2e:
- ike.Code = KeyDelete
- case 0x24:
- ike.Code = KeyHome
- case 0x23:
- ike.Code = KeyEnd
- case 0x21:
- ike.Code = KeyPgup
- case 0x22:
- ike.Code = KeyPgdn
- case 0x26:
- ike.Code = KeyArrowUp
- case 0x28:
- ike.Code = KeyArrowDown
- case 0x25:
- ike.Code = KeyArrowLeft
- case 0x27:
- ike.Code = KeyArrowRight
- case 0x5b:
- ike.Code = KeyCommand
- }
- return ike
- }
- // OS specific magic
- // =================
- var kernel32 = syscall.NewLazyDLL("kernel32.dll")
- var syscallFunc = syscall.Syscall6
- /*
- createEvent creates an event object.
- https://msdn.microsoft.com/en-gb/library/windows/desktop/ms682396(v=vs.85).aspx
- */
- func createEvent() (syscall.Handle, error) {
- var err error
- r0, _, e1 := syscallFunc(kernel32.NewProc("CreateEventW").Addr(),
- 4, 0, 0, 0, 0, 0, 0)
- if int(r0) == 0 {
- if e1 != 0 {
- err = error(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return syscall.Handle(r0), err
- }
- /*
- waitForMultipleObjects waits (100ms) for an input event. Returns if an event was
- received and any encountered errors.
- https://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
- */
- func waitForMultipleObjects(objects []syscall.Handle) (bool, error) {
- var err error
- r0, _, e1 := syscall.Syscall6(kernel32.NewProc("WaitForMultipleObjects").Addr(), 4,
- uintptr(len(objects)), uintptr(unsafe.Pointer(&objects[0])), 0, 10, 0, 0)
- if uint32(r0) == 0xFFFFFFFF {
- if e1 != 0 {
- err = error(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return uint32(r0) != 0x00000102 && err == nil, err
- }
- /*
- winConsoleScreenBufferInfo is a CONSOLE_SCREEN_BUFFER_INFO which contains
- information about a console screen buffer.
- https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
- */
- type winConsoleScreenBufferInfo struct {
- size winCOORD
- cursorPosition winCOORD
- attributes uint16
- window struct {
- left int16
- top int16
- right int16
- bottom int16
- }
- maximumWindowSize winCOORD
- }
- var tempWinConsoleScreenBufferInfo = &winConsoleScreenBufferInfo{} // Temp space to prevent unnecessary heap allocations
- /*
- getConsoleScreenBufferInfo retrieves information about the specified console
- screen buffer.
- https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
- */
- func getConsoleScreenBufferInfo(h syscall.Handle) (*winConsoleScreenBufferInfo, error) {
- var err error
- var ret *winConsoleScreenBufferInfo
- r0, _, e1 := syscall.Syscall(kernel32.NewProc("GetConsoleScreenBufferInfo").Addr(), 2,
- uintptr(h), uintptr(unsafe.Pointer(tempWinConsoleScreenBufferInfo)), 0)
- if int(r0) == 0 {
- if e1 != 0 {
- err = error(e1)
- } else {
- err = syscall.EINVAL
- }
- } else {
- ret = tempWinConsoleScreenBufferInfo
- }
- return ret, err
- }
- /*
- winCOORD is COORD structure which defines the coordinates of a character cell
- in a console screen buffer.
- https://docs.microsoft.com/en-us/windows/console/coord-str
- */
- type winCOORD struct {
- x int16
- y int16
- }
- /*
- setConsoleCursorPosition sets the cursor position.
- https://docs.microsoft.com/en-us/windows/console/setconsolecursorposition
- */
- func setConsoleCursorPosition(h syscall.Handle, pos *winCOORD) error {
- var err error
- r0, _, e1 := syscall.Syscall(kernel32.NewProc("SetConsoleCursorPosition").Addr(), 2,
- uintptr(h), uintptr(*(*int32)(unsafe.Pointer(pos))), 0)
- if int(r0) == 0 {
- if e1 != 0 {
- err = error(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return err
- }
- /*
- inputRecord is a record read with readConsoleInput
- https://docs.microsoft.com/en-us/windows/console/input-record-str
- */
- type inputRecord struct {
- eventType uint16
- _ [2]byte
- event [16]byte
- }
- /*
- keyEventWin interprets the event from inputRecord as a windows key event
- https://docs.microsoft.com/en-us/windows/console/key-event-record-str
- */
- type winKeyEvent struct {
- keyDown int32 // key is pressed
- repeatCount uint16 // repeat count, which indicates that a key is being held down
- virtualKeyCode uint16 // identifies the given key in a device-independent manner
- virtualScanCode uint16 // represents the device-dependent value generated by the keyboard hardware
- unicodeChar uint16 // translated Unicode character
- controlKeyState uint32 // state of the control keys
- }
- var temp uint32 // Temp space to prevent unnecessary heap allocations
- /*
- readConsoleInput reads data from a console input buffer.
- https://docs.microsoft.com/en-us/windows/console/readconsoleinput
- */
- func readConsoleInput(h syscall.Handle, ir *inputRecord) error {
- var err error
- r0, _, e1 := syscall.Syscall6(kernel32.NewProc("ReadConsoleInputW").Addr(),
- 4, uintptr(h), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&temp)), 0, 0)
- if int(r0) == 0 {
- if e1 != 0 {
- err = error(e1)
- } else {
- err = syscall.EINVAL
- }
- }
- return err
- }
|