Skip to content
Merged
Next Next commit
Implement termios.tcgetwinsize/tcsetwinsize
Adds ioctl(TIOCGWINSZ/TIOCSWINSZ) wrappers in host_env and the
corresponding Python-facing functions in the termios stdlib module,
removing the associated expectedFailure markers in test_termios.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbzcYLnX5tMfTM9BP7FDTT
  • Loading branch information
sigmaith and claude committed Jul 22, 2026
commit d09e9b814c551bc0fdf405729158bd70286fd4f3
4 changes: 0 additions & 4 deletions Lib/test/test_termios.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ def writer():
'output was not resumed')
self.assertEqual(os.read(rfd, 1024), b'def')

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'termios' has no attribute 'tcgetwinsize'
def test_tcgetwinsize(self):
size = termios.tcgetwinsize(self.fd)
self.assertIsInstance(size, tuple)
Expand All @@ -230,22 +229,19 @@ def test_tcgetwinsize(self):
self.assertIsInstance(size[1], int)
self.assertEqual(termios.tcgetwinsize(self.stream), size)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'termios' has no attribute 'tcgetwinsize'
def test_tcgetwinsize_errors(self):
self.assertRaisesTermiosError(errno.ENOTTY, termios.tcgetwinsize, self.bad_fd)
self.assertRaises(ValueError, termios.tcgetwinsize, -1)
self.assertRaises(OverflowError, termios.tcgetwinsize, 2**1000)
self.assertRaises(TypeError, termios.tcgetwinsize, object())
self.assertRaises(TypeError, termios.tcgetwinsize)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'termios' has no attribute 'tcgetwinsize'
def test_tcsetwinsize(self):
size = termios.tcgetwinsize(self.fd)
termios.tcsetwinsize(self.fd, size)
termios.tcsetwinsize(self.fd, list(size))
termios.tcsetwinsize(self.stream, size)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'termios' has no attribute 'tcgetwinsize'
def test_tcsetwinsize_errors(self):
size = termios.tcgetwinsize(self.fd)
self.assertRaises(TypeError, termios.tcsetwinsize, self.fd, size[:-1])
Expand Down
24 changes: 24 additions & 0 deletions crates/host_env/src/termios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,27 @@ pub fn tcflush(fd: i32, queue: i32) -> std::io::Result<()> {
pub fn tcflow(fd: i32, action: i32) -> std::io::Result<()> {
::termios::tcflow(fd, action)
}

pub fn tcgetwinsize(fd: i32) -> std::io::Result<(u16, u16)> {
let mut size: libc::winsize = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::ioctl(fd, TIOCGWINSZ as _, &mut size) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
Comment thread
sigmaith marked this conversation as resolved.
Outdated
Ok((size.ws_row, size.ws_col))
}

pub fn tcsetwinsize(fd: i32, row: u16, col: u16) -> std::io::Result<()> {
let mut size: libc::winsize = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::ioctl(fd, TIOCGWINSZ as _, &mut size) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
Comment thread
sigmaith marked this conversation as resolved.
Outdated
size.ws_row = row;
size.ws_col = col;
let ret = unsafe { libc::ioctl(fd, TIOCSWINSZ as _, &size) };
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
Comment thread
sigmaith marked this conversation as resolved.
Outdated
Ok(())
}
Comment on lines +178 to +191

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

27 changes: 27 additions & 0 deletions crates/stdlib/src/termios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,33 @@ mod termios {
Ok(())
}

#[pyfunction]
fn tcgetwinsize(fd: PyObjectRef, vm: &VirtualMachine) -> PyResult<(u16, u16)> {
let fd = Fildes::try_from_object(vm, fd).map(Into::into)?;
Comment thread
sigmaith marked this conversation as resolved.
Outdated
let size = host_termios::tcgetwinsize(fd).map_err(|e| termios_error(e, vm))?;
Ok(size)
}

#[pyfunction]
fn tcsetwinsize(fd: PyObjectRef, size: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let fd = Fildes::try_from_object(vm, fd).map(Into::into)?;
let size = vm.extract_elements_with(&size, Ok)?;
let [row, col] = <[PyObjectRef; 2]>::try_from(size)
.map_err(|_| vm.new_type_error("tcsetwinsize: size must be 2 element tuple/list"))?;
Comment thread
sigmaith marked this conversation as resolved.
Outdated
Comment thread
sigmaith marked this conversation as resolved.
Outdated

let row: u16 = row
.downcast_ref::<PyInt>()
.ok_or_else(|| vm.new_type_error("tcsetwinsize: winsize values must be integers"))?
.try_to_primitive(vm)?;
Comment thread
sigmaith marked this conversation as resolved.
Outdated
let col: u16 = col
.downcast_ref::<PyInt>()
.ok_or_else(|| vm.new_type_error("tcsetwinsize: winsize values must be integers"))?
.try_to_primitive(vm)?;
Comment thread
sigmaith marked this conversation as resolved.
Outdated
Comment thread
sigmaith marked this conversation as resolved.
Outdated

host_termios::tcsetwinsize(fd, row, col).map_err(|e| termios_error(e, vm))?;
Ok(())
}

fn termios_error(err: std::io::Error, vm: &VirtualMachine) -> PyBaseExceptionRef {
vm.new_os_subtype_error(
error_type(vm),
Expand Down
Loading