Determining Terminal Size
Problem
Within a terminal-based application, you want to find the size of the terminal: how many rows and columns are available for you to draw on.
Solution
This is easy if you e using the Curses library. This example uses the Curses.program wrapper described in Recipe 21.5:
Curses.program do |scr| max_y, max_x = scr.maxy, scr.maxx scr.setpos(0, 0) scr.addstr("Your terminal size is #{max_x}x#{max_y}. Press any key to exit.") scr.getch end
Its a little less easy with Ncurses: you have to pass in two arrays to the underlying C libraries, and extract the numbers from the arrays. Again, this example uses the Ncurses wrapper from Recipe 21.5:
Ncurses.program do |scr| max_y, max_x = [], [] scr.getmaxyx(max_y, max_x) max_y, max_x = max_y[0], max_x[0] str = "Your terminal size is #{max_x}x#{max_y}. Press any key to exit." scr.mvaddstr(0, 0, str) scr.getch end
If you e not using a Curses-style library, its not easy at all.
Discussion
If you plan to simulate graphical elements on a textual terminal, subdivide it into virtual windows, or print justified output, youll need to know the terminals dimensions. For decades, the standard terminal size has been 25 rows by 80 columns, but modern GUIs and high screen resolutions let users create text terminals of almost any size. Its okay to enforce a minimum terminal size, but its a bad idea to assume that the terminal is any specific size.
The terminal size is a very useful piece of information to have, but its not an easy one to get. The Curses library was written to solve this kind of problem, but if you e willing to go into the operating system API, or if you e on Windows where Curses is not a standard feature, you can find the terminal size without letting a Curses-style library take over your whole application.
On Unix systems (including Mac OS X), you can make an ioctl system call to get the terminal size. Since you e calling out to the underlying operating system, youll need to use strange constants and C-like structures to carry the response:
TIOCGWINSZ = 0x5413 # For an Intel processor # TIOCGWINSZ = 0x40087468 # For a PowerPC processor def terminal_size rows, cols = 25, 80 buf = [ 0, 0, 0, 0 ].pack("SSSS") if STDOUT.ioctl(TIOCGWINSZ, buf) >= 0 then rows, cols, row_pixels, col_pixels = buf.unpack("SSSS")[0..1] end return rows, cols end terminal_size # => [21, 80]
Here, the methods pack and unpack convert between a four-element array and a string that is modified in-place by the ioctl call. After the call, the first two elements of the array contain the number of rows and columns for the terminal. Note that the first argument to ioctl is architecture-dependent.
The Windows version works the same way, although you must jump through more hoops and the system call returns a much bigger data structure:
STDOUT_HANDLE = 0xFFFFFFF5 def terminal_size m_GetStdHandle = Win32API.new(kernel32, GetStdHandle, [L], L) m_GetConsoleScreenBufferInfo = Win32API.new (kernel32, GetConsoleScreenBufferInfo, [L, P], L ) format = SSSSSssssSS buf = ([0] * format.size).pack(format) stdout_handle = m_GetStdHandle.call(STDOUT_HANDLE) m_GetConsoleScreenBufferInfo.call(stdout_handle, buf) (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = buf.unpack(format) return bottom - top + 1, right - left + 1 end terminal_size # => [25, 80]
If all else fails, on Unix systems you can call out to the stty command:
def terminal_size %x{stty size}.split.collect { |x| x.to_i } end terminal_size # => [21, 80]
See Also
- The ioctl code is based on code posted to ruby-talk by Paul Brannan (http://blade.nagaokaut.ac.jp/cgi-bin/rcat.rb/ruby/ruby-talk/40350)
- The Windows code is based on code in the Win32API_Console library, a simple Ruby wrapper around Windows console-related API calls (http://rb-w32mod.sourceforge.net/)
- Recipe 21.5, "Setting Up and Tearing Down a Curses Program"
Категории