How to disable ENABLE_LINE_INPUT in ConPTY? How to call GetConsoleMode for a PseudoConsole? #17602

Closed
opened 2026-01-31 05:47:27 +00:00 by claunia · 3 comments
Owner

Originally created by @lonnywong on GitHub (May 30, 2022).

Windows Terminal version

1.12.10982.0

Windows build number

10.0.19043.0

Other Software

https://github.com/UserExistsError/conpty v0.1.0

Is it similar to https://stackoverflow.com/a/11439517/7696611 ?

Steps to reproduce

  • go run conptytest.go
package main

import (
        "bufio"
        "context"
        "fmt"
        "strings"

        "github.com/UserExistsError/conpty"
)

func main() {
        cpty, err := conpty.Start("python3 -c \"import sys; s = sys.stdin.readline(); print(len(s))\"")
        if err != nil {
                fmt.Printf("Failed to spawn a pty:  %v", err)
                return
        }
        defer cpty.Close()

        go func() {
                cpty.Write([]byte(strings.Repeat("A", 3000) + "\r\n"))

                scanner := bufio.NewScanner(cpty)
                for scanner.Scan() {
                        // Should output 3001.
                        // But output 511 in cmd, output 2510 in MSYS2.
                        fmt.Printf("Output: %s\n", scanner.Text())
                }
                if err := scanner.Err(); err != nil {
                        fmt.Println(err)
                }
        }()

        if _, err := cpty.Wait(context.Background()); err != nil {
                fmt.Printf("Error: %v", err)
        }
}

Expected Behavior

Output: 3001

Actual Behavior

Output: 511

Originally created by @lonnywong on GitHub (May 30, 2022). ### Windows Terminal version 1.12.10982.0 ### Windows build number 10.0.19043.0 ### Other Software https://github.com/UserExistsError/conpty v0.1.0 Is it similar to https://stackoverflow.com/a/11439517/7696611 ? ### Steps to reproduce * `go run conptytest.go` ```go package main import ( "bufio" "context" "fmt" "strings" "github.com/UserExistsError/conpty" ) func main() { cpty, err := conpty.Start("python3 -c \"import sys; s = sys.stdin.readline(); print(len(s))\"") if err != nil { fmt.Printf("Failed to spawn a pty: %v", err) return } defer cpty.Close() go func() { cpty.Write([]byte(strings.Repeat("A", 3000) + "\r\n")) scanner := bufio.NewScanner(cpty) for scanner.Scan() { // Should output 3001. // But output 511 in cmd, output 2510 in MSYS2. fmt.Printf("Output: %s\n", scanner.Text()) } if err := scanner.Err(); err != nil { fmt.Println(err) } }() if _, err := cpty.Wait(context.Background()); err != nil { fmt.Printf("Error: %v", err) } } ``` ### Expected Behavior Output: 3001 ### Actual Behavior Output: 511
claunia added the Issue-QuestionNeeds-TriageNeeds-Tag-FixProduct-Conpty labels 2026-01-31 05:47:27 +00:00
Author
Owner

@lonnywong commented on GitHub (May 31, 2022):

If disable ENABLE_LINE_INPUT in the child process, it works.

But I can't change the child process to disable ENABLE_LINE_INPUT.

Is there a way to disable ENABLE_LINE_INPUT in ConPTY?

  • go build readline.go
package main

import (
	"bufio"
	"fmt"
	"os"

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

func main() {
	var st uint32
	if err := windows.GetConsoleMode(windows.Handle(os.Stdin.Fd()), &st); err != nil {
		fmt.Println(err)
		return
	}

	// disable ENABLE_LINE_INPUT
	raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT)
	fmt.Printf("st: %#x, raw: %#x\n", st, raw)

	if err := windows.SetConsoleMode(windows.Handle(os.Stdin.Fd()), raw); err != nil {
		fmt.Println(err)
		return
	}

	// reset console mode
	defer func() {
		if err := windows.SetConsoleMode(windows.Handle(os.Stdin.Fd()), st); err != nil {
			fmt.Println(err)
			return
		}
	}()

	// read line
	line, err := bufio.NewReader(os.Stdin).ReadBytes('\r')
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("\nLength: %d\r\n", len(line))
}
  • go run conptytest.go
package main

import (
	"bufio"
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/UserExistsError/conpty"
)

func main() {
	cpty, err := conpty.Start("readline.exe") // The child process have to disable ENABLE_LINE_INPUT first
	if err != nil {
		fmt.Printf("Failed to spawn a pty:  %v", err)
		return
	}
	defer cpty.Close()

	go func() {
		time.Sleep(1 * time.Second) // Wait the child process disable ENABLE_LINE_INPUT first
		cpty.Write([]byte(strings.Repeat("A", 5000) + "\r\n"))

		scanner := bufio.NewScanner(cpty)
		for scanner.Scan() {
			fmt.Println(scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			fmt.Println(err)
		}
	}()

	if _, err := cpty.Wait(context.Background()); err != nil {
		fmt.Printf("Error: %v", err)
	}
}
@lonnywong commented on GitHub (May 31, 2022): If disable `ENABLE_LINE_INPUT` in the child process, it works. But I can't change the child process to disable `ENABLE_LINE_INPUT`. Is there a way to disable `ENABLE_LINE_INPUT` in ConPTY? * `go build readline.go` ```go package main import ( "bufio" "fmt" "os" "golang.org/x/sys/windows" ) func main() { var st uint32 if err := windows.GetConsoleMode(windows.Handle(os.Stdin.Fd()), &st); err != nil { fmt.Println(err) return } // disable ENABLE_LINE_INPUT raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT) fmt.Printf("st: %#x, raw: %#x\n", st, raw) if err := windows.SetConsoleMode(windows.Handle(os.Stdin.Fd()), raw); err != nil { fmt.Println(err) return } // reset console mode defer func() { if err := windows.SetConsoleMode(windows.Handle(os.Stdin.Fd()), st); err != nil { fmt.Println(err) return } }() // read line line, err := bufio.NewReader(os.Stdin).ReadBytes('\r') if err != nil { fmt.Println(err) return } fmt.Printf("\nLength: %d\r\n", len(line)) } ``` * `go run conptytest.go` ```go package main import ( "bufio" "context" "fmt" "strings" "time" "github.com/UserExistsError/conpty" ) func main() { cpty, err := conpty.Start("readline.exe") // The child process have to disable ENABLE_LINE_INPUT first if err != nil { fmt.Printf("Failed to spawn a pty: %v", err) return } defer cpty.Close() go func() { time.Sleep(1 * time.Second) // Wait the child process disable ENABLE_LINE_INPUT first cpty.Write([]byte(strings.Repeat("A", 5000) + "\r\n")) scanner := bufio.NewScanner(cpty) for scanner.Scan() { fmt.Println(scanner.Text()) } if err := scanner.Err(); err != nil { fmt.Println(err) } }() if _, err := cpty.Wait(context.Background()); err != nil { fmt.Printf("Error: %v", err) } } ```
Author
Owner

@zadjii-msft commented on GitHub (Jun 9, 2022):

But I can't change the child process to disable ENABLE_LINE_INPUT.

Huh. I think you might be stuck here then. I don't think we provide any sort of way to call the console APIs on a conpty you create. There might be some trickery possible. You might be able to attach some other helper disable-line-input.exe to the same conpty, which just calls that API, and have it magically work. IIRC input mode is a global state, so multiple attached clients can mess with each other's state. But, I'd also caution that's definitely a hack and one we're intending to fix (#4954)

@zadjii-msft commented on GitHub (Jun 9, 2022): > But I can't change the child process to disable `ENABLE_LINE_INPUT`. Huh. I think you might be stuck here then. I don't think we provide any sort of way to call the console APIs on a conpty you create. There might be some trickery possible. You might be able to attach some other helper `disable-line-input.exe` to the same conpty, which just calls that API, and have it magically work. IIRC input mode is a global state, so multiple attached clients can mess with each other's state. But, I'd also caution that's definitely a hack and one we're intending to fix (#4954)
Author
Owner

@lonnywong commented on GitHub (Jun 9, 2022):

But I can't change the child process to disable ENABLE_LINE_INPUT.

Huh. I think you might be stuck here then. I don't think we provide any sort of way to call the console APIs on a conpty you create. There might be some trickery possible. You might be able to attach some other helper disable-line-input.exe to the same conpty, which just calls that API, and have it magically work. IIRC input mode is a global state, so multiple attached clients can mess with each other's state. But, I'd also caution that's definitely a hack and one we're intending to fix (#4954)

Thanks for your help.

I tried it before, it works.

I disabled the ENABLE_LINE_INPUT and create a ssh process.
But the /usr/bin/ssh in MSYS2 still can't read all the input from ConPTY.
Looks like it's another issue.

Btw, the C:\Windows\System32\OpenSSH\ssh.exe works good.

@lonnywong commented on GitHub (Jun 9, 2022): > > But I can't change the child process to disable `ENABLE_LINE_INPUT`. > > Huh. I think you might be stuck here then. I don't think we provide any sort of way to call the console APIs on a conpty you create. There might be some trickery possible. You might be able to attach some other helper `disable-line-input.exe` to the same conpty, which just calls that API, and have it magically work. IIRC input mode is a global state, so multiple attached clients can mess with each other's state. But, I'd also caution that's definitely a hack and one we're intending to fix (#4954) Thanks for your help. I tried it before, it works. I disabled the `ENABLE_LINE_INPUT` and create a `ssh` process. But the `/usr/bin/ssh` in `MSYS2` still can't read all the input from ConPTY. Looks like it's another issue. Btw, the `C:\Windows\System32\OpenSSH\ssh.exe` works good.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#17602