in reply to Failing to get current TTY's rows & columns...

See ioctl, if the return value is true, its not an error.

See also Pack/Unpack Tutorial (aka How the System Stores Data), s!* looks suspicious, what I'd usually do is try every option :)

update: Oh look, this is a perlfaq8 item

How do I get the screen size?

If you have Term::ReadKey module installed from CPAN, you can use it to fetch the width and height in characters and in pixels:

use Term::ReadKey; ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();
This is more portable than the raw ioctl, but not as illustrative:
require 'sys/ioctl.ph'; die "no TIOCGWINSZ " unless defined &TIOCGWINSZ; open(TTY, "+</dev/tty") or die "No tty: $!"; unless (ioctl(TTY, &TIOCGWINSZ, $winsize='')) { die sprintf "$0: ioctl TIOCGWINSZ (%08x: $!)\n", &TIOCGWIN +SZ; } ($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize); print "(row,col) = ($row,$col)"; print " (xpixel,ypixel) = ($xpixel,$ypixel)" if $xpixel || $ypixe +l; print "\n";

Replies are listed 'Best First'.
Re^2: Failing to get current TTY's rows & columns...
by perl-diddler (Chaplain) on Apr 14, 2011 at 02:10 UTC
    So the answer here is that I have to open "/dev/tty" each time I call it rather than reference my already-open "STDOUT"?

    Hmmm....I'm disappointed that I need to open another file handle and can't get the rows & columns associated with my already open FH, "STDOUT", directly...

    As for using 'Term::ReadKey', I thought of using something along those lines (or a Curses func), but wanted something that was minimal overhead, as this was called every loop, and portability isn't a major consideration, considering the nature of the program this is being used in is showing linux process and mounted-dev I/O.

    If I look at /proc/$$/fd/1, I see a softlink to /dev/pts/1 (i.e. a character, tty type device). Indeed, if I use your 2nd prog and substitute in "/dev/pts/1" instead of "/dev/tty", I get the same result (ie. it works).

    Shouldn't STDIN, STDOUT map to fd[0], fd1 => /dev/pts/1 on some level?

    I.e. Shouldn't I be able to use some mapping function on STDOUT to get a file descriptor that's suitable for ioctl?

      So the answer here is that I have to open "/dev/tty" each time I call it rather than reference my already-open "STDOUT"?

      I don't have linux, but no, I don't think that is the answer.

      I think part of the answer is to do error checking correctly like the faq entry

      I also think using S4 is part of the answer, not being an expert on pack/unpack and not having a /dev/tty, I can't run any tests.

      Hmmm....I'm disappointed that I need to open another file handle and can't get the rows & columns associated with my already open FH, "STDOUT", directly...

      Sounds premature if you ask me, avoiding abstractions always involves some growing pains :)

      Shouldn't STDIN, STDOUT map to fd[0], fd[1] => /dev/pts/1 on some level?

      They do

      $ perl -le " print fileno($_) for STDIN, STDOUT, STDERR " 0 1 2
      See, fd 0,1,2. Now regarding tty, using operator -t to test if filehandle is opened to a tty, you can see 0,1,2 are connected to a tty
      $ perl -le " print 0+-t $_ for STDIN, STDOUT, STDERR " 1 1 1
      but not when you do redirection, here is STDIN not connected to a tty
      $ perl -le " print 0+-t $_ for STDIN, STDOUT, STDERR " < NUL 0 1 1
      here is STDIN and STDERR not connected to a tty
      $ perl -le " print 0+-t $_ for STDIN, STDOUT, STDERR " < NUL 2> NUL 0 1 0
      here is STDOUT not connected to a tty
      $ perl -le " print 0+-t $_ for STDIN, STDOUT, STDERR " > out.txt $ cat out.txt 1 0 1
      I.e. Shouldn't I be able to use some mapping function on STDOUT to get a file descriptor that's suitable for ioctl?

      ioctl says you don't need to, the faq item doesn't, and neither does Term::Size::Any

        I think part of the answer is to do error checking correctly like the faq entry
        See my answer to previous poster -- that's a red herring as well as the unpack format.

        What you are missing is:

        "I.e. Shouldn't I be able to use some mapping function on STDOUT to get a file descriptor that's suitable for ioctl?
        ioctl says you don't need to, the faq item doesn't, and neither does Term::Size::Any
        The faq item doesn't use STDOUT. It doesn't show how one would use STDOUT to determine the terminal's size. It opens '/dev/tty' -- ANOTHER file handle -- it doesn't show how to determine the size of the terminal attached to STDOUT.

        Of note -- if STDOUT was redirected to a file, then trying to determine its size woudn't return valid values (presumably the ioctl would return an error).

        So how would you determine the rows/columns of whatever STDOUT is pointing to (without opening another character device like "/dev/tty")?

        Even if you aren't on linux, presumably there's a way to do it on your platform, no? I.e. if you have a 'tty' window, how would you determine the size of the window addressed by 'STDOUT'?

Re^2: Failing to get current TTY's rows & columns...
by perl-diddler (Chaplain) on Apr 15, 2011 at 22:00 UTC
    See ioctl, if the return value is true, its not an error.
    My manpage says:
    RETURN VALUE Usually, on success zero is returned. A few ioctl() requests +use the return value as an output parameter and return a non-negative +value on success. On error, -1 is returned, and errno is set appropriat +ely.
    Note the phrase: "Usually, on success, zero is returned."... Some return a value as an output param, but this call isn't one of them. My error check code appears to be valid according to the above, though I noticed an oddity if you print the value returning from ioctl in this code:
    require 'sys/ioctl.ph'; die "no TIOCGWINSZ " unless defined &TIOCGWINSZ; open(TTY, "+</dev/tty") or die "No tty: $!"; my $ret=ioctl(TTY, &TIOCGWINSZ, $winsize=''); printf "ret num=%d, retstring=%s\n",$ret,$ret; unless ($ret) { die sprintf "$0: ioctl TIOCGWINSZ (%08x: $!)\n", &TIOCGWIN +SZ; } ($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize); print "(row,col) = ($row,$col)"; print " (xpixel,ypixel) = ($xpixel,$ypixel)" if $xpixel || $ypixe +l; print "\n";
    The output is:
    ret num=0, retstring=0 but true (row,col) = (79,80)
    That is bizarre! 0 = true?!

    In any event, that's a red herring, as my code works when I use "/dev/tty" as a device instead of STDOUT. Same goes for the unpack format.

    I used S!*, which is likely "more correct" than S4 shown above.

    The "!" after the "S" says to use the platform's native size for a short instead of fixed a fixed 16-bit size. Since the struct is declared as a short on my platform, I figured it was best to use my platform's native "short" size rather than a fixed 16 bit value -- wouldn't you agree??

    The "*" vs. "4", makes no difference in this case as there are only 4 values to receive the output. "*" tells unpack to unpack as many as are in the struct -- so if the struct was 6 shorts long instead of 4, it would return 6 values, the last 2 of which would be thrown away as I only have an array of 4 vars to receive it. It would be like doing:

    my @ar=(1,2,3,4,5,6); my ($x,$y,$z)=@ar;
    $x-$z get values 1 through 3, and values 4,5,6 get ignored.

    All of the above covers the stuff that "doesn't make a difference". Then comes the part that doesn't make sense.

    As you note, fileno() on STD(IN,OUT,ERR) all return 0,1,2 as expected. Then why can't I use ioctl(STDOUT) with ioctl and have it work?

      My manpage says:

      Manpage? I already linked to ioctl before, perldoc -f ioctl