get the terminal's size

eryk sun eryksun at gmail.com
Mon Jan 14 21:35:15 EST 2019


On 1/14/19, Schachner, Joseph <Joseph.Schachner at teledyne.com> wrote:
> I just tested the fix I proposed, in Python 2.7.13
>
> Code:
> from win32api import GetSystemMetrics
>
> def main():
>     print "Width =", GetSystemMetrics(0)
>     print "Height =", GetSystemMetrics(1)

That gets the monitor size, i.e:

    SM_CXSCREEN (0)
    The width of the screen of the primary display monitor, in pixels.

    SM_CYSCREEN (1)
    The height of the screen of the primary display monitor, in pixels.

The console's visible window is a rectangular view on its active
screen buffer. We have to query the screen-buffer information to
obtain the coordinates of this rectangle (right, left, bottom, top).
Python's os.get_terminal_size does this, but I dislike the fact that
it uses the process standard handles instead of C file descriptors and
only supports 0, 1, and 2.

Here's a version that defaults to opening the console's active screen
buffer, "CONOUT$". Otherwise it uses msvcrt.get_osfhandle to get the
handle for the fd argument instead of hard-mapping 0, 1, and 2 to the
process standard handles. For symmetry, I've also added POSIX code
that switches the default to opening "/dev/tty" instead of using
stdout.

import os
import ctypes
from collections import namedtuple

terminal_size = namedtuple('terminal_size', 'columns lines')

if os.name == 'posix':

    import tty
    import fcntl

    class winsize(ctypes.Structure):
        _fields_ = (('ws_row', ctypes.c_ushort),
                    ('ws_col', ctypes.c_ushort),
                    ('ws_xpixel', ctypes.c_ushort),
                    ('ws_ypixel', ctypes.c_ushort))

    def get_terminal_size(fd=None):
        """Return the size of the terminal window as (columns, lines).

        The optional argument fd specifies which file descriptor should
        be queried. An OSError is raised if the file descriptor is not
        connected to a terminal (Unix) or a console screen buffer
        (Windows). The default behavior is to open and query the process
        controlling terminal (i.e. Unix "/dev/tty") or active console
        screen buffer (i.e. Windows "CONOUT$").
        """
        w = winsize()
        if fd is None:
            fd_used = os.open('/dev/tty', os.O_RDWR)
        else:
            fd_used = fd
        try:
            fcntl.ioctl(fd_used, tty.TIOCGWINSZ, w)
        finally:
            if fd is None:
                os.close(fd_used)
        return terminal_size(w.ws_col, w.ws_row)

elif os.name == 'nt':

    import msvcrt
    from ctypes import wintypes
    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

    class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
        _fields_ = (('dwSize', wintypes._COORD),
                    ('dwCursorPosition', wintypes._COORD),
                    ('wAttributes', wintypes.WORD),
                    ('srWindow', wintypes.SMALL_RECT),
                    ('dwMaximumWindowSize', wintypes._COORD))

    kernel32.GetConsoleScreenBufferInfo.argtypes = (
        wintypes.HANDLE, ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO))

    def get_terminal_size(fd=None):
        """Return the size of the terminal window as (columns, lines).

        The optional argument fd specifies which file descriptor should
        be queried. An OSError is raised if the file descriptor is not
        connected to a terminal (Unix) or a console screen buffer
        (Windows). The default behavior is to open and query the process
        controlling terminal (i.e. Unix "/dev/tty") or active console
        screen buffer (i.e. Windows "CONOUT$").
        """
        csbi = CONSOLE_SCREEN_BUFFER_INFO()
        w = csbi.srWindow
        if fd is None:
            fd_used = os.open('CONOUT$', os.O_RDWR)
        else:
            fd_used = fd
        try:
            h = msvcrt.get_osfhandle(fd_used)
            if not kernel32.GetConsoleScreenBufferInfo(h,
                                ctypes.byref(csbi)):
                raise ctypes.WinError(ctypes.get_last_error())
        finally:
            if fd is None:
                os.close(fd_used)
        return terminal_size(w.Right - w.Left + 1, w.Bottom - w.Top + 1)



More information about the Python-list mailing list