Ruby-termios

Hi,

I wrote a program on Linux using ruby-termios which was supposed to run on
Solaris 9. Short description: It doesn't.

Long description: I use ruby-termios to change some terminal settings for
my simple user interface, a menu in this case. It looks like this:

    def fetch
        letter = nil

        print @data['text']
        menuMode {
            letter = $stdin.readchar.chr
            until @data['options'].include?(letter.upcase) do
                letter = $stdin.readchar.chr
            end
        }

        return letter
    end

In this part, @data['text'] contains a menu text, @data['options'] is a
list of valid characters. Here is the termios part of the code:

    def menuMode
        term = Termios::getattr($stdin)
        oldterm = term

        term.c_lflag &= ~(Termios::ECHO | Termios::ICANON)

        Termios::setattr($stdin, Termios::TCSANOW, term)

        yield

        Termios::setattr($stdin, Termios::TCSANOW, oldterm)
        Termios::flush($stdin, Termios::TCIOFLUSH)
    end

The result on Linux is what I expected: I get the characters one by one and
can work on them before they are displayed or in any buffer I care for.

Some strange things happen on Solaris: First of all, my debugging shows
that something changes the termios flags where I don't expect it:

    def menuMode
        term = Termios::getattr($stdin)
        oldterm = term
        @logger.debug("c_lflag is #{term.c_lflag}")

        term.c_lflag &= ~(Termios::ECHO | Termios::ICANON)

        Termios::setattr($stdin, Termios::TCSANOW, term)

        yield

        @logger.debug("c_lflag is #{oldterm.c_lflag}")
        Termios::setattr($stdin, Termios::TCSANOW, oldterm)
        Termios::flush($stdin, Termios::TCIOFLUSH)
    end

Here is a fragment from the logfile:

D, [2006-06-13T15:45:23.732487 #18771] DEBUG -- : c_lflag is 35387
D, [2006-06-13T15:45:29.987362 #18771] DEBUG -- : c_lflag is 35377

Why are the two flags different?

Second, some buffering is going on. If I enter a character, the program
blocks at the readchar. If I run this version of fetch()

     1 def fetch
     2 letter = nil
       
     3 print @data['text']
     4 menuMode {
     5 @logger.debug("Enter yielded block")
     6 letter = $stdin.readchar.chr
     7 @logger.debug("Read in letter '#{letter}'")
       
     8 until @data['options'].include?(letter.upcase) do
     9 @logger.debug("Enter loop block")
    10 letter = $stdin.readchar.chr
    11 @logger.debug("Read in letter '#{letter}'")
    12 end
    13 }
       
    14 return letter
    15 end

...the program blocks at lines 6 and 10 until I enter four characters. Then
all input is passed to the program, so for an invalid character the result
looks like this:

D, [2006-06-13T15:55:09.230396 #19028] DEBUG -- : Enter yielded block
D, [2006-06-13T15:55:13.383131 #19028] DEBUG -- : Read in letter 't'
D, [2006-06-13T15:55:13.383816 #19028] DEBUG -- : Enter loop block
D, [2006-06-13T15:55:13.384062 #19028] DEBUG -- : Read in letter 't'
D, [2006-06-13T15:55:13.384284 #19028] DEBUG -- : Enter loop block
D, [2006-06-13T15:55:13.384498 #19028] DEBUG -- : Read in letter 't'
D, [2006-06-13T15:55:13.384710 #19028] DEBUG -- : Enter loop block
D, [2006-06-13T15:55:13.384918 #19028] DEBUG -- : Read in letter 't'
D, [2006-06-13T15:55:13.385132 #19028] DEBUG -- : Enter loop block

For valid character this means that the first chracter picks a menu option
and the next three characters are entered in whatever is behind that menu
option. Nearly unusable and pretty dangerous.

At the moment I don't know where to look next. It may be some simple
omission, but I can't find it, so any ideas are appreciated. Please let me
know if you need any other information, the debugging is a bit awkward, but
I didn't dare to call the debugger with broken terminal.

Thanks in advance!

Thorsten Radiohead: Subterranean Homesick Alien

···

--
Be alert - Some terrorists look normal.

In Message-Id: <20060706215534.GD2241@eumel.yoo.local>
Thorsten Haude <ruby@thorstenhau.de> writes:

        oldterm = term
        @logger.debug("c_lflag is #{term.c_lflag}")

(snip)

        term.c_lflag &= ~(Termios::ECHO | Termios::ICANON)
        @logger.debug("c_lflag is #{oldterm.c_lflag}")

(snip)

Why are the two flags different?

Since YOU clear ECHO and ICANON. Note both oldterm and term are
pointing a same Termios structure.

Second, some buffering is going on. If I enter a character, the program
blocks at the readchar. If I run this version of fetch()

(snip)

...the program blocks at lines 6 and 10 until I enter four characters. Then
all input is passed to the program, so for an invalid character the result
looks like this:

Are you sure VMIN and VTIME are properly set? From termios(4) on
NetBSD:

   Noncanonical Mode Input Processing
     In noncanonical mode input processing, input bytes are not assembled into
     lines, and erase and kill processing does not occur. The values of the
     VMIN and VTIME members of the c_cc array are used to determine how to
     process the bytes received.

     VMIN represents the minimum number of bytes that should be received when
     the read(2) system call successfully returns. VTIME is a timer of 0.1
     second granularity that is used to time out bursty and short term data
     transmissions. (snip)

Well, that's my wild guess....

···

--
kjana@dm4lab.to July 7, 2006
Time is illusion, life is confusion.