aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/peterh/liner/input_windows.go
blob: 36e95161adf5100e40cac3cc221818b171da48e9 (plain) (tree)
1
2
3
4
5
6
7





                 
                       





                                                     







                                                                                             




















































































                                                                             
                              






                                                                           
                                   





















                        
                        
                        
                        












                                                                                                      














                                                                                                            










                                                 

                            
















                                                                                        










                                                                                                    




                                                                                              


                                                                                                       


                                                                                                       


                                                                                                       



                                                                                                       







                                                                                        




































































                                                                                                                                

























































                                                                                  

                         
package liner

import (
    "bufio"
    "os"
    "syscall"
    "unicode/utf16"
    "unsafe"
)

var (
    kernel32 = syscall.NewLazyDLL("kernel32.dll")

    procGetStdHandle                  = kernel32.NewProc("GetStdHandle")
    procReadConsoleInput              = kernel32.NewProc("ReadConsoleInputW")
    procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents")
    procGetConsoleMode                = kernel32.NewProc("GetConsoleMode")
    procSetConsoleMode                = kernel32.NewProc("SetConsoleMode")
    procSetConsoleCursorPosition      = kernel32.NewProc("SetConsoleCursorPosition")
    procGetConsoleScreenBufferInfo    = kernel32.NewProc("GetConsoleScreenBufferInfo")
    procFillConsoleOutputCharacter    = kernel32.NewProc("FillConsoleOutputCharacterW")
)

// These names are from the Win32 api, so they use underscores (contrary to
// what golint suggests)
const (
    std_input_handle     = uint32(-10 & 0xFFFFFFFF)
    std_output_handle    = uint32(-11 & 0xFFFFFFFF)
    std_error_handle     = uint32(-12 & 0xFFFFFFFF)
    invalid_handle_value = ^uintptr(0)
)

type inputMode uint32

// State represents an open terminal
type State struct {
    commonState
    handle      syscall.Handle
    hOut        syscall.Handle
    origMode    inputMode
    defaultMode inputMode
    key         interface{}
    repeat      uint16
}

const (
    enableEchoInput      = 0x4
    enableInsertMode     = 0x20
    enableLineInput      = 0x2
    enableMouseInput     = 0x10
    enableProcessedInput = 0x1
    enableQuickEditMode  = 0x40
    enableWindowInput    = 0x8
)

// NewLiner initializes a new *State, and sets the terminal into raw mode. To
// restore the terminal to its previous state, call State.Close().
func NewLiner() *State {
    var s State
    hIn, _, _ := procGetStdHandle.Call(uintptr(std_input_handle))
    s.handle = syscall.Handle(hIn)
    hOut, _, _ := procGetStdHandle.Call(uintptr(std_output_handle))
    s.hOut = syscall.Handle(hOut)

    s.terminalSupported = true
    if m, err := TerminalMode(); err == nil {
        s.origMode = m.(inputMode)
        mode := s.origMode
        mode &^= enableEchoInput
        mode &^= enableInsertMode
        mode &^= enableLineInput
        mode &^= enableMouseInput
        mode |= enableWindowInput
        mode.ApplyMode()
    } else {
        s.inputRedirected = true
        s.r = bufio.NewReader(os.Stdin)
    }

    s.getColumns()
    s.outputRedirected = s.columns <= 0

    return &s
}

// These names are from the Win32 api, so they use underscores (contrary to
// what golint suggests)
const (
    focus_event              = 0x0010
    key_event                = 0x0001
    menu_event               = 0x0008
    mouse_event              = 0x0002
    window_buffer_size_event = 0x0004
)

type input_record struct {
    eventType uint16
    pad       uint16
    blob      [16]byte
}

type key_event_record struct {
    KeyDown         int32
    RepeatCount     uint16
    VirtualKeyCode  uint16
    VirtualScanCode uint16
    Char            uint16
    ControlKeyState uint32
}

// These names are from the Win32 api, so they use underscores (contrary to
// what golint suggests)
const (
    vk_tab    = 0x09
    vk_menu   = 0x12 // ALT key
    vk_prior  = 0x21
    vk_next   = 0x22
    vk_end    = 0x23
    vk_home   = 0x24
    vk_left   = 0x25
    vk_up     = 0x26
    vk_right  = 0x27
    vk_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
    bKey      = 0x42
    dKey      = 0x44
    fKey      = 0x46
    yKey      = 0x59
)

const (
    shiftPressed     = 0x0010
    leftAltPressed   = 0x0002
    leftCtrlPressed  = 0x0008
    rightAltPressed  = 0x0001
    rightCtrlPressed = 0x0004

    modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed
)

// inputWaiting only returns true if the next call to readNext will return immediately.
func (s *State) inputWaiting() bool {
    var num uint32
    ok, _, _ := procGetNumberOfConsoleInputEvents.Call(uintptr(s.handle), uintptr(unsafe.Pointer(&num)))
    if ok == 0 {
        // call failed, so we cannot guarantee a non-blocking readNext
        return false
    }

    // during a "paste" input events are always an odd number, and
    // the last one results in a blocking readNext, so return false
    // when num is 1 or 0.
    return num > 1
}

func (s *State) readNext() (interface{}, error) {
    if s.repeat > 0 {
        s.repeat--
        return s.key, nil
    }

    var input input_record
    pbuf := uintptr(unsafe.Pointer(&input))
    var rv uint32
    prv := uintptr(unsafe.Pointer(&rv))

    var surrogate uint16

    for {
        ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv)

        if ok == 0 {
            return nil, err
        }

        if input.eventType == window_buffer_size_event {
            xy := (*coord)(unsafe.Pointer(&input.blob[0]))
            s.columns = int(xy.x)
            return winch, nil
        }
        if input.eventType != key_event {
            continue
        }
        ke := (*key_event_record)(unsafe.Pointer(&input.blob[0]))
        if ke.KeyDown == 0 {
            if ke.VirtualKeyCode == vk_menu && ke.Char > 0 {
                // paste of unicode (eg. via ALT-numpad)
                if surrogate > 0 {
                    return utf16.DecodeRune(rune(surrogate), rune(ke.Char)), nil
                } else if utf16.IsSurrogate(rune(ke.Char)) {
                    surrogate = ke.Char
                    continue
                } else {
                    return rune(ke.Char), nil
                }
            }
            continue
        }

        if ke.VirtualKeyCode == vk_tab && ke.ControlKeyState&modKeys == shiftPressed {
            s.key = shiftTab
        } else if ke.VirtualKeyCode == bKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
            ke.ControlKeyState&modKeys == rightAltPressed) {
            s.key = altB
        } else if ke.VirtualKeyCode == dKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
            ke.ControlKeyState&modKeys == rightAltPressed) {
            s.key = altD
        } else if ke.VirtualKeyCode == fKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
            ke.ControlKeyState&modKeys == rightAltPressed) {
            s.key = altF
        } else if ke.VirtualKeyCode == yKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
            ke.ControlKeyState&modKeys == rightAltPressed) {
            s.key = altY
        } else if ke.Char > 0 {
            if surrogate > 0 {
                s.key = utf16.DecodeRune(rune(surrogate), rune(ke.Char))
            } else if utf16.IsSurrogate(rune(ke.Char)) {
                surrogate = ke.Char
                continue
            } else {
                s.key = rune(ke.Char)
            }
        } else {
            switch ke.VirtualKeyCode {
            case vk_prior:
                s.key = pageUp
            case vk_next:
                s.key = pageDown
            case vk_end:
                s.key = end
            case vk_home:
                s.key = home
            case vk_left:
                s.key = left
                if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
                    if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
                        s.key = wordLeft
                    }
                }
            case vk_right:
                s.key = right
                if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
                    if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
                        s.key = wordRight
                    }
                }
            case vk_up:
                s.key = up
            case vk_down:
                s.key = down
            case vk_insert:
                s.key = insert
            case vk_delete:
                s.key = del
            case vk_f1:
                s.key = f1
            case vk_f2:
                s.key = f2
            case vk_f3:
                s.key = f3
            case vk_f4:
                s.key = f4
            case vk_f5:
                s.key = f5
            case vk_f6:
                s.key = f6
            case vk_f7:
                s.key = f7
            case vk_f8:
                s.key = f8
            case vk_f9:
                s.key = f9
            case vk_f10:
                s.key = f10
            case vk_f11:
                s.key = f11
            case vk_f12:
                s.key = f12
            default:
                // Eat modifier keys
                // TODO: return Action(Unknown) if the key isn't a
                // modifier.
                continue
            }
        }

        if ke.RepeatCount > 1 {
            s.repeat = ke.RepeatCount - 1
        }
        return s.key, nil
    }
}

// Close returns the terminal to its previous mode
func (s *State) Close() error {
    s.origMode.ApplyMode()
    return nil
}

func (s *State) startPrompt() {
    if m, err := TerminalMode(); err == nil {
        s.defaultMode = m.(inputMode)
        mode := s.defaultMode
        mode &^= enableProcessedInput
        mode.ApplyMode()
    }
}

func (s *State) restartPrompt() {
}

func (s *State) stopPrompt() {
    s.defaultMode.ApplyMode()
}

// TerminalSupported returns true because line editing is always
// supported on Windows.
func TerminalSupported() bool {
    return true
}

func (mode inputMode) ApplyMode() error {
    hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
    if hIn == invalid_handle_value || hIn == 0 {
        return err
    }
    ok, _, err := procSetConsoleMode.Call(hIn, uintptr(mode))
    if ok != 0 {
        err = nil
    }
    return err
}

// TerminalMode returns the current terminal input mode as an InputModeSetter.
//
// This function is provided for convenience, and should
// not be necessary for most users of liner.
func TerminalMode() (ModeApplier, error) {
    var mode inputMode
    hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
    if hIn == invalid_handle_value || hIn == 0 {
        return nil, err
    }
    ok, _, err := procGetConsoleMode.Call(hIn, uintptr(unsafe.Pointer(&mode)))
    if ok != 0 {
        err = nil
    }
    return mode, err
}

const cursorColumn = true