Skip to content

Commit

Permalink
Fix blockless IO::FileDescriptor echo and raw mode methods (#14529)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed Apr 24, 2024
1 parent dcccbc5 commit 82a208c
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 29 deletions.
6 changes: 6 additions & 0 deletions src/crystal/system/file_descriptor.cr
@@ -1,9 +1,15 @@
# :nodoc:
module Crystal::System::FileDescriptor
# Enables input echoing if *enable* is true, disables it otherwise.
# private def system_echo(enable : Bool)

# For the duration of the block, enables input echoing if *enable* is true,
# disables it otherwise.
# private def system_echo(enable : Bool, & : ->)

# Enables raw mode if *enable* is true, enables cooked mode otherwise.
# private def system_raw(enable : Bool)

# For the duration of the block, enables raw mode if *enable* is true, enables
# cooked mode otherwise.
# private def system_raw(enable : Bool, & : ->)
Expand Down
40 changes: 25 additions & 15 deletions src/crystal/system/unix/file_descriptor.cr
Expand Up @@ -257,29 +257,39 @@ module Crystal::System::FileDescriptor
io
end

private def system_echo(enable : Bool, mode = nil)
new_mode = mode || FileDescriptor.tcgetattr(fd)
flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL
new_mode.c_lflag = enable ? (new_mode.c_lflag | flags) : (new_mode.c_lflag & ~flags)
if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(new_mode)) != 0
raise IO::Error.from_errno("tcsetattr")
end
end

private def system_echo(enable : Bool, & : ->)
system_console_mode do |mode|
flags = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL
mode.c_lflag = enable ? (mode.c_lflag | flags) : (mode.c_lflag & ~flags)
if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0
raise IO::Error.from_errno("tcsetattr")
end
system_echo(enable, mode)
yield
end
end

private def system_raw(enable : Bool, mode = nil)
new_mode = mode || FileDescriptor.tcgetattr(fd)
if enable
new_mode = FileDescriptor.cfmakeraw(new_mode)
else
new_mode.c_iflag |= LibC::BRKINT | LibC::ISTRIP | LibC::ICRNL | LibC::IXON
new_mode.c_oflag |= LibC::OPOST
new_mode.c_lflag |= LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN
end
if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(new_mode)) != 0
raise IO::Error.from_errno("tcsetattr")
end
end

private def system_raw(enable : Bool, & : ->)
system_console_mode do |mode|
if enable
mode = FileDescriptor.cfmakeraw(mode)
else
mode.c_iflag |= LibC::BRKINT | LibC::ISTRIP | LibC::ICRNL | LibC::IXON
mode.c_oflag |= LibC::OPOST
mode.c_lflag |= LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL | LibC::ICANON | LibC::ISIG | LibC::IEXTEN
end
if FileDescriptor.tcsetattr(fd, LibC::TCSANOW, pointerof(mode)) != 0
raise IO::Error.from_errno("tcsetattr")
end
system_raw(enable, mode)
yield
end
end
Expand Down
8 changes: 8 additions & 0 deletions src/crystal/system/wasi/file_descriptor.cr
Expand Up @@ -40,10 +40,18 @@ module Crystal::System::FileDescriptor
raise NotImplementedError.new "Crystal::System::File#flock"
end

private def system_echo(enable : Bool)
raise NotImplementedError.new "Crystal::System::FileDescriptor#system_echo"
end

private def system_echo(enable : Bool, & : ->)
raise NotImplementedError.new "Crystal::System::FileDescriptor#system_echo"
end

private def system_raw(enable : Bool)
raise NotImplementedError.new "Crystal::System::FileDescriptor#system_raw"
end

private def system_raw(enable : Bool, & : ->)
raise NotImplementedError.new "Crystal::System::FileDescriptor#system_raw"
end
Expand Down
39 changes: 29 additions & 10 deletions src/crystal/system/win32/file_descriptor.cr
Expand Up @@ -301,41 +301,60 @@ module Crystal::System::FileDescriptor
io
end

private def system_echo(enable : Bool)
system_console_mode(enable, LibC::ENABLE_ECHO_INPUT, 0)
end

private def system_echo(enable : Bool, & : ->)
system_console_mode(enable, LibC::ENABLE_ECHO_INPUT, 0) { yield }
end

private def system_raw(enable : Bool)
system_console_mode(enable, LibC::ENABLE_VIRTUAL_TERMINAL_INPUT, LibC::ENABLE_PROCESSED_INPUT | LibC::ENABLE_LINE_INPUT | LibC::ENABLE_ECHO_INPUT)
end

private def system_raw(enable : Bool, & : ->)
system_console_mode(enable, LibC::ENABLE_VIRTUAL_TERMINAL_INPUT, LibC::ENABLE_PROCESSED_INPUT | LibC::ENABLE_LINE_INPUT | LibC::ENABLE_ECHO_INPUT) { yield }
end

@[AlwaysInline]
private def system_console_mode(enable, on_mask, off_mask, &)
private def system_console_mode(enable, on_mask, off_mask, old_mode = nil)
windows_handle = self.windows_handle
if LibC.GetConsoleMode(windows_handle, out old_mode) == 0
raise IO::Error.from_winerror("GetConsoleMode")
unless old_mode
if LibC.GetConsoleMode(windows_handle, out mode) == 0
raise IO::Error.from_winerror("GetConsoleMode")
end
old_mode = mode
end

old_on_bits = old_mode & on_mask
old_off_bits = old_mode & off_mask
if enable
return yield if old_on_bits == on_mask && old_off_bits == 0
return if old_on_bits == on_mask && old_off_bits == 0
new_mode = (old_mode | on_mask) & ~off_mask
else
return yield if old_on_bits == 0 && old_off_bits == off_mask
return if old_on_bits == 0 && old_off_bits == off_mask
new_mode = (old_mode | off_mask) & ~on_mask
end

if LibC.SetConsoleMode(windows_handle, new_mode) == 0
raise IO::Error.from_winerror("SetConsoleMode")
end
end

@[AlwaysInline]
private def system_console_mode(enable, on_mask, off_mask, &)
windows_handle = self.windows_handle
if LibC.GetConsoleMode(windows_handle, out old_mode) == 0
raise IO::Error.from_winerror("GetConsoleMode")
end

ret = yield
if LibC.GetConsoleMode(windows_handle, pointerof(old_mode)) != 0
new_mode = (old_mode & ~on_mask & ~off_mask) | old_on_bits | old_off_bits
LibC.SetConsoleMode(windows_handle, new_mode)
begin
system_console_mode(enable, on_mask, off_mask, old_mode)
yield
ensure
LibC.SetConsoleMode(windows_handle, old_mode)
end
ret
end
end

Expand Down
8 changes: 4 additions & 4 deletions src/io/console.cr
Expand Up @@ -31,7 +31,7 @@ class IO::FileDescriptor < IO
#
# Raises `IO::Error` if this `IO` is not a terminal device.
def noecho! : Nil
system_echo(false) { return }
system_echo(false)
end

# Enables character echoing on this `IO`.
Expand All @@ -40,7 +40,7 @@ class IO::FileDescriptor < IO
#
# Raises `IO::Error` if this `IO` is not a terminal device.
def echo! : Nil
system_echo(true) { return }
system_echo(true)
end

# Yields `self` to the given block, enables character processing for the
Expand Down Expand Up @@ -75,7 +75,7 @@ class IO::FileDescriptor < IO
#
# Raises `IO::Error` if this `IO` is not a terminal device.
def cooked! : Nil
system_raw(false) { return }
system_raw(false)
end

# Enables raw mode on this `IO`.
Expand All @@ -86,7 +86,7 @@ class IO::FileDescriptor < IO
#
# Raises `IO::Error` if this `IO` is not a terminal device.
def raw! : Nil
system_raw(true) { return }
system_raw(true)
end

@[Deprecated]
Expand Down

0 comments on commit 82a208c

Please sign in to comment.