ruby:以原始模式向 PTY 发送击键
ruby: Sending keystrokes to PTY in raw mode
我正在尝试向 ruby-newt
模块添加一些自动化测试。我的代码似乎可以工作,但仍然需要在终端手动点击 ENTER
才能完成。
例如下面的代码,\t
会切换到下一个按钮,\r
会按下按钮,两个命令都执行成功,但是ENTER
键还需要在终端手动按下,否则程序将无限期挂起。
如果wr.write "\t\r"
行被注释掉,则程序超时,10秒后成功退出。我试过 wr.flush
,但这没有用。我也试过在命令中包含 \n
。
我应该在 write
命令中包含任何其他内容以确保子程序成功接收它吗?
require 'newt'
require 'pty'
def newt_run
begin
Newt::Screen.new
Newt::Screen.centered_window(20, 15, 'Button')
b1 = Newt::Button.new(1, 1, 'Button1')
b2 = Newt::Button.new(1, 6, 'Button2')
b = Newt::Button.new(1, 11, 'Exit')
f = Newt::Form.new
f.set_timer(10000)
f.add(b1, b2, b)
rv = f.run
ensure
Newt::Screen.finish
end
end
master, slave = PTY.open
rd, wr = IO.pipe
if fork.nil? then
master.close
wr.close
$stdin.reopen(rd)
$stdout.reopen(slave)
$stderr.reopen(slave)
newt_run
else
slave.close
rd.close
wr.write "\t\r"
Process.wait
end
问题是 newt C 库默认打开 /dev/tty
进行输入。它不使用标准输入。这就是为什么您发送的任何内容似乎都不起作用。它不是在读取您的管道,而是在读取 /dev/tty
.
这里是更详细的问题:
- Ruby newt 在 C-lib
中调用 newtInit()
newtInit()
呼叫 SLang_init_tty
SLang_init_tty
默认始终将输入附加到 /dev/tty
。
如果你 SLang_init_tty
的 read the documentation 你会发现变量 SLang_TT_Read_FD
决定是否使用 /dev/tty
。
解决方案 1:
在从 Ruby 调用 newtInit()
之前,您需要将 SLang_TT_Read_FD
设置为 stdin
。
方案二:
使用setsid
和ioctl(TIOCSCTTY)
重新分配派生进程中的控制终端(参见docs for ioctl here)。
工作示例:
TIOCSCTTY = 0x540E
master, slave = PTY.open
if fork.nil? then
# Child process
# Make group leader. Required for aquiring controlling TTY.
Process.setsid
master.close # Close master side
STDIN.reopen(slave) # Reassign STDIN
STDIN.ioctl(TIOCSCTTY, 0) # Reassign controlling TTY (important part)
# Ensure master is ready
slave.gets
# Now we can run the UI
newt_run
else
# Parent process
slave.close
# Sync up with slave
master.puts 'hello'
# Allow for UI setup
sleep 1
master.write "\e[B" # Arrow down
master.write "\e[B" # Arrow down
master.write "\t" # Tab
master.write "\r" # Enter
Process.wait
end
我正在尝试向 ruby-newt
模块添加一些自动化测试。我的代码似乎可以工作,但仍然需要在终端手动点击 ENTER
才能完成。
例如下面的代码,\t
会切换到下一个按钮,\r
会按下按钮,两个命令都执行成功,但是ENTER
键还需要在终端手动按下,否则程序将无限期挂起。
如果wr.write "\t\r"
行被注释掉,则程序超时,10秒后成功退出。我试过 wr.flush
,但这没有用。我也试过在命令中包含 \n
。
我应该在 write
命令中包含任何其他内容以确保子程序成功接收它吗?
require 'newt'
require 'pty'
def newt_run
begin
Newt::Screen.new
Newt::Screen.centered_window(20, 15, 'Button')
b1 = Newt::Button.new(1, 1, 'Button1')
b2 = Newt::Button.new(1, 6, 'Button2')
b = Newt::Button.new(1, 11, 'Exit')
f = Newt::Form.new
f.set_timer(10000)
f.add(b1, b2, b)
rv = f.run
ensure
Newt::Screen.finish
end
end
master, slave = PTY.open
rd, wr = IO.pipe
if fork.nil? then
master.close
wr.close
$stdin.reopen(rd)
$stdout.reopen(slave)
$stderr.reopen(slave)
newt_run
else
slave.close
rd.close
wr.write "\t\r"
Process.wait
end
问题是 newt C 库默认打开 /dev/tty
进行输入。它不使用标准输入。这就是为什么您发送的任何内容似乎都不起作用。它不是在读取您的管道,而是在读取 /dev/tty
.
这里是更详细的问题:
- Ruby newt 在 C-lib 中调用
newtInit()
呼叫SLang_init_tty
SLang_init_tty
默认始终将输入附加到/dev/tty
。
newtInit()
如果你 SLang_init_tty
的 read the documentation 你会发现变量 SLang_TT_Read_FD
决定是否使用 /dev/tty
。
解决方案 1:
在从 Ruby 调用 newtInit()
之前,您需要将 SLang_TT_Read_FD
设置为 stdin
。
方案二:
使用setsid
和ioctl(TIOCSCTTY)
重新分配派生进程中的控制终端(参见docs for ioctl here)。
工作示例:
TIOCSCTTY = 0x540E
master, slave = PTY.open
if fork.nil? then
# Child process
# Make group leader. Required for aquiring controlling TTY.
Process.setsid
master.close # Close master side
STDIN.reopen(slave) # Reassign STDIN
STDIN.ioctl(TIOCSCTTY, 0) # Reassign controlling TTY (important part)
# Ensure master is ready
slave.gets
# Now we can run the UI
newt_run
else
# Parent process
slave.close
# Sync up with slave
master.puts 'hello'
# Allow for UI setup
sleep 1
master.write "\e[B" # Arrow down
master.write "\e[B" # Arrow down
master.write "\t" # Tab
master.write "\r" # Enter
Process.wait
end