Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/sys/windows: Neither Read() nor ReadConsole() read special keys #44373

Closed
scrouthtv opened this issue Feb 18, 2021 · 7 comments
Closed

x/sys/windows: Neither Read() nor ReadConsole() read special keys #44373

scrouthtv opened this issue Feb 18, 2021 · 7 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@scrouthtv
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.16 windows/amd64

Does this issue reproduce with the latest release?

Yes. It also happens to me on Go 1.15.4

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\lenni\AppData\Local\go-build
set GOENV=C:\Users\lenni\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\lenni\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\lenni\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Users\lenni\scoop\apps\go\current
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Users\lenni\scoop\apps\go\current\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.16
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=D:\Programmieren\termios\go.mod
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\lenni\AppData\Local\Temp\go-build2957794104=/tmp/go-build -gno-record-gcc-switches

What did you do?

I am currently writing a simple command-line editor in Go. For this, I want my program to be able to read every of the user's keypresses. For this, I think I have to set the Console Mode to only ENABLE_WINDOW_INPUT.

Here is my code (sorry for inline code, I think Go Playground is not really applicable):

  package main

  import "golang.org/x/sys/windows"

  var in windows.Handle
  var oldMode uint32

 func main() {
    err := SetRaw()
    if err != nil {
      panic(err)
    }

    buf := make([]uint16, 20)
    Read(buf)
    os.Stdout.Write([]byte("Read"))
    Close()
  }

  func SetRaw() error {
    var err error
    in, err = windows.Open("CONIN$", windows.O_RDWR, 0)
    if err != nil {
      return err
    }

    err = windows.GetConsoleMode(in, &oldMode)

    err = windows.SetConsoleMode(in, windows.ENABLE_WINDOW_INPUT)
    if err != nil {
      return err
    }

    return nil
  }

  // Read reads a single keypress
  func Read(p []uint16) (int, error) {
    //return windows.Read(in, p)
    var tmp_arg uint32
    var inputControl byte = 0
    err := windows.ReadConsole(in, &p[0], 1, &tmp_arg, &inputControl)
    return 0, err
  }

  func Close() {
    windows.SetConsoleMode(in, oldMode)
    windows.Close(in)
  }

What did you expect to see?

I'd expect this program to start, and close after I pressed an arrow key.

What did you see instead?

The program starts, but does only exit if I press a letter key (a-z, 0-9). Special keys as F1 through F12, the arrow keys or home/end/pgup/pgdown don't work.

If I set the console mode to ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT instead, the behaviour is as expected, but then I don't have support on older Windows devices (7, 8, ...)

Thanks for any help in advance.

@gopherbot gopherbot added this to the Unreleased milestone Feb 18, 2021
@toothrot toothrot added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Feb 18, 2021
@toothrot
Copy link
Contributor

/cc @alexbrainman

@scrouthtv
Copy link
Author

scrouthtv commented Feb 18, 2021

Here's a bit more. The old way would be to set the console mode and read from the console via the deprecated syscall package:

const enable_window_input = 0x8

var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var proc_get_console_mode = kernel32.NewProc("GetConsoleMode")
var proc_set_console_mode = kernel32.NewProc("SetConsoleMode")
var proc_read_console_input = kernel32.NewProc("ReadConsoleInputW")

func get_console_mode(h syscall.Handle, mode *dword) (err error) {
	r0, _, e1 := syscall.Syscall(proc_get_console_mode.Addr(),
	2, uintptr(h), uintptr(unsafe.Pointer(mode)), 0)
	if int(r0) == 0 {
		if e1 != 0 {
			err = error(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

func set_console_mode(h syscall.Handle, mode dword) (err error) {
	r0, _, e1 := syscall.Syscall(proc_set_console_mode.Addr(),
	2, uintptr(h), uintptr(mode), 0)
	if int(r0) == 0 {
		if e1 != 0 {
			err = error(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

func read_console_input(h syscall.Handle, record *input_record) (err error) {
	r0, _, e1 := syscall.Syscall6(proc_read_console_input.Addr(),
	4, uintptr(h), uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&tmp_arg)), 0, 0)
	if int(r0) == 0 {
		if e1 != 0 {
			err = error(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}

This way I can read the arrow keys, and it works on Windows 7.

See the full demo in github.com/scrouthtv/termios/win_arrows

@scrouthtv
Copy link
Author

scrouthtv commented Feb 18, 2021

Found the difference: Both ways use the very same code. Except that x/sys/windows uses "ReadConsoleW" which [https://docs.microsoft.com/en-us/windows/console/readconsole](reads character input from the console) and my example uses "ReadConsoleInputW" which [https://docs.microsoft.com/en-us/windows/console/readconsoleinput](reads data from the console).

Because of backwards compatibility, I'd suggest introducing a new method to the x/sys/windows package called ReadConsoleInput which uses "ReadConsoleInputW" syscall and documenting both methods accordingly:

// ReadConsole reads characters from the console

// ReadConsoleInput reads any input from the console

@scrouthtv
Copy link
Author

Okay, both functions don't really work for me:

  • ReadConsole only reads characters but no other keypresses (think arrow keys, erase, home/end, ...)
  • ReadConsoleInput reads any input but currently returns only garbage - I get random combinations of 0x0 and 0x1 when reading characters. It also sends data on key release.

@alexbrainman
Copy link
Member

@scrouthtv and @toothrot I cannot help here. This is a question about Windows programming. And I am unfamiliar with this area of Windows.

Alex

@toothrot
Copy link
Contributor

@scrouthtv It sounds like this might be better answered if seen by more Go Windows developers, maybe on one of our forums: https://golang.org/wiki/Questions. I know that relatively few Go programmers follow issues as closely as they do the resources listed there. It sounds like this is something someone must have run into before.

There still may be an issue here, but I personally lack the Windows expertise to know what it is.

@scrouthtv
Copy link
Author

scrouthtv commented Feb 18, 2021

For future reference: https://forum.golangbridge.org/t/windows-raw-console-i-o/22487.

Thanks for your time

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants