英文:
How to handle arrow keys in a Go terminal app running on Windows 2012 or earlier?
问题
我有一个Go终端应用程序,在进行远程连接后,将终端设置为TTY原始模式,并通过连接传递按键。
我遇到的问题是,在运行于2012年之前的Windows操作系统上,我无法传递箭头键。在较新的Windows版本中,您可以设置ENABLE_VIRTUAL_TERMINAL_INPUT,然后检测并传递箭头键,但在2012年或更早的版本中,此选项不起作用。对于这些版本,无法检测到箭头键。
是否有一种解决方法,而不使用VS Studio?
澄清一下,我正在寻找操作系统中允许此功能的机制,而不是可能实现此机制的库。
英文:
I have a Go terminal application that after it makes remote connect, puts the terminal into a TTY Raw mode and just passes keystrokes through the conenction.
The issue I am having is that I cannot pass arrow keys when running on an Windows OS before 2012. In later Windows versions you can set ENABLE_VIRTUAL_TERMINAL_INPUT and arrows keys are detected then passed, but this option does not on 2012 or earlier. For those versions no arrow key is detected.
Is their a work around for this without using VS Studio?
To clarify, I am looking for the mechanism in the OS to allow this not a library may implement the mechanism.
答案1
得分: 1
检查一下containerd/console是否可以帮助,使用它的最新提交。
它被一个类似charmbracelet/wish的库使用,并且可以在设置原始模式时检测并设置或不设置ENABLE_VIRTUAL_TERMINAL_INPUT。
如果charmbracelet/wishlist(基于wish)可以在你的环境中(在2012年之前的Windows操作系统)工作,那么containerd/console也有可能对你自己的应用程序有所帮助。
两个重要的函数是:
func (m *master) initStdios() {
	// 注意:我们忽略控制台模式警告,因为输入/输出可以被重定向。
	//
	// TODO: 调查打开CONOUT$/CONIN$以正确处理此问题
	m.in = windows.Handle(os.Stdin.Fd())
	if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil {
		// 验证是否支持windows.ENABLE_VIRTUAL_TERMINAL_INPUT,但不设置它。
		if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
			vtInputSupported = true
		}
		// 即使失败也无条件地将控制台模式设置回来,因为SetConsoleMode会记住输入句柄上的无效位。
		windows.SetConsoleMode(m.in, m.inMode)
	}
	m.out = windows.Handle(os.Stdout.Fd())
	if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil {
		if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
			m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
		} else {
			windows.SetConsoleMode(m.out, m.outMode)
		}
	}
	m.err = windows.Handle(os.Stderr.Fd())
	if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil {
		if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
			m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
		} else {
			windows.SetConsoleMode(m.err, m.errMode)
		}
	}
}
其中master定义为:
type master struct {
	in     windows.Handle
	inMode uint32
	out     windows.Handle
	outMode uint32
	err     windows.Handle
	errMode uint32
}
还有:
func makeInputRaw(fd windows.Handle, mode uint32) error
// makeInputRaw将与给定文件描述符连接的终端(Windows控制台)设置为原始模式
func makeInputRaw(fd windows.Handle, mode uint32) error {
	// 参考
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	// 禁用这些模式
	mode &^= windows.ENABLE_ECHO_INPUT
	mode &^= windows.ENABLE_LINE_INPUT
	mode &^= windows.ENABLE_MOUSE_INPUT
	mode &^= windows.ENABLE_WINDOW_INPUT
	mode &^= windows.ENABLE_PROCESSED_INPUT
	// 启用这些模式
	mode |= windows.ENABLE_EXTENDED_FLAGS
	mode |= windows.ENABLE_INSERT_MODE
	mode |= windows.ENABLE_QUICK_EDIT_MODE
	if vtInputSupported {
		mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
	}
	if err := windows.SetConsoleMode(fd, mode); err != nil {
		return fmt.Errorf("无法将控制台设置为原始模式:%w", err)
	}
	return nil
}
英文:
Check if containerd/console can help, using its latest commits.
It is used by a library like charmbracelet/wish, and can detect and set or not ENABLE_VIRTUAL_TERMINAL_INPUT when setting raw mode.
If can make charmbracelet/wishlist (based on wish) work in your environment (with a Windows OS before 2012), chances are containerd/console can also help with you own application.
The two important functions are:
func (m *master) initStdios() {
	// Note: We discard console mode warnings, because in/out can be redirected.
	//
	// TODO: Investigate opening CONOUT$/CONIN$ to handle this correctly
	m.in = windows.Handle(os.Stdin.Fd())
	if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil {
		// Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
		if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
			vtInputSupported = true
		}
		// Unconditionally set the console mode back even on failure because SetConsoleMode
		// remembers invalid bits on input handles.
		windows.SetConsoleMode(m.in, m.inMode)
	}
	m.out = windows.Handle(os.Stdout.Fd())
	if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil {
		if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
			m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
		} else {
			windows.SetConsoleMode(m.out, m.outMode)
		}
	}
	m.err = windows.Handle(os.Stderr.Fd())
	if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil {
		if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
			m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
		} else {
			windows.SetConsoleMode(m.err, m.errMode)
		}
	}
}
with master being:
type master struct {
	in     windows.Handle
	inMode uint32
	out     windows.Handle
	outMode uint32
	err     windows.Handle
	errMode uint32
}
And:
func makeInputRaw(fd windows.Handle, mode uint32) error
// makeInputRaw puts the terminal (Windows Console) connected to the given
// file descriptor into raw mode
func makeInputRaw(fd windows.Handle, mode uint32) error {
	// See
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	// Disable these modes
	mode &^= windows.ENABLE_ECHO_INPUT
	mode &^= windows.ENABLE_LINE_INPUT
	mode &^= windows.ENABLE_MOUSE_INPUT
	mode &^= windows.ENABLE_WINDOW_INPUT
	mode &^= windows.ENABLE_PROCESSED_INPUT
	// Enable these modes
	mode |= windows.ENABLE_EXTENDED_FLAGS
	mode |= windows.ENABLE_INSERT_MODE
	mode |= windows.ENABLE_QUICK_EDIT_MODE
	if vtInputSupported {
		mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
	}
	if err := windows.SetConsoleMode(fd, mode); err != nil {
		return fmt.Errorf("unable to set console to raw mode: %w", err)
	}
	return nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论