Turn off echo on StdIn.Read

This is really for either Linux or Mac…

When I use StdIn.Read (or ReadLine, ReadAll, or just Input), whatever is typed is automatically echoed to the console. Unfortunately this means you can’t create a password input that doesn’t echo anything back or parse the ANSI codes that are generated when you use, for example, delete or an arrow key.

Has anyone dealt with this without plugins? Any easy solution?

Normally, the stty command is your friend here. Specifically the echo|-echo options

stty -echo

However, I’ve never been able to achieve a solution with either stdin or in the shell, I suspect that Xojo’s stdin/stdout/stderr channels are not attached to a terminal. This means that we should probably request stty control from Xojo similar to how they’ve exposed the canonical setting.

I appreciate the thoughts, but I think you lost me at “normally”. :slight_smile:

I did get that this warrants a feature request though. I was hoping someone had stumbled on some declares that could make this happen now.

My stty is your regex :stuck_out_tongue:

Unfortunately, the Xojo team have to expose that setting since we can’t just execute stty in the shell.

Maybe I could add an option for my StdInMBS class?

While I would love that, I think Kem is trying to stay away from plugins for this project.

(If you see Ken from me in reference to any of your posts, sorry - new Mac and autocorrect still tries to override my typing…)


[code]dim s as new StdoutMBS
s.Write “Hello World”+chr(10)

dim t as string
dim n as integer = StdinMBS.ReadString(t)
s.Write chr(10)
s.Write "Read with echo: "+t+chr(10)

StdinMBS.Echo = false

n = StdinMBS.ReadString(t)
s.Write chr(10)
s.Write "Read without echo: "+t+chr(10)

StdinMBS.Echo = true[/code]

Of course you can try same in declare, but well, I just added it for my class for Mac, Windows and Linux to make it cross platform.

Cool for me :), but I don’t believe that declares are an option for raw Xojo since we would need to be able to hook into stdin/out/err.

I did some testing and there is a way to fake an invisible password with some funny escape sequences.

Have a look at this sample:

[code]stdout.WriteLine “Enter password:”

stdout.Writeline “Start hidden for Input:”+Chr(27)+"[8m"
Dim pass As String = stdin.ReadLine

stdout.Writeline Chr(27)+"[0m" + “End hidden after Input.”

stdout.Write Chr(27)+"[1A" // CURSOR UP
stdout.Write Chr(27)+"[;0A" // CURSOR LEFT COLUMN
stdout.Write Chr(27)+"[K" // EREASE TO END OF LINE

stdout.WriteLine “Got password “”” + pass + “”"."

It makes the input hidden. The characters are still on the terminal.
So it does afterwards set the cursor at the line where the password is entered and erase everything. So there is no copy & past from the invisible chars possible.

Does this help?

BTW: This solution does not need any Plugin.

Thanks. I do something like this already and haven’t tried your code, but mine suffers from the problem of a multi line password. There is also the issue of properly dealing with backspace and the like in any type of input.

“multi line password” sounds funny…
Backspace etc. should be no issue because it is just writing but hidden.

In case you need to remove several lines you can just call several times

Perhaps you can check the line count to the password and then call the erase escapes “count” times…

Interesting, I must always use StdIn.ReadLine or one of the variations (Read, ReadAll) where backspace will be returned as 0x7F rather than performing the action. Using Input is better for that purpose, so that helps somewhat.

(In another app, I read characters in a loop so I must use ReadAll to keep the processing going, but that’s a different story.)

The problem with trying to erase each line that makes up the password is that you must assume the width of the console. If the password is 79 characters, that may well be one line, but perhaps it’s two or three depending on the width.

These are, of course, workarounds rather than the true solution I was hoping for, but since we’re going down that path, I’m thinking of a system that clears the screen to start, then prints everything in a buffer starting from 1, 1. After the password is entered, a line like “Password: ****” is added to the buffer and the whole screen is erased and redrawn with the new buffer. This will still fail if the password is multi-page, but I don’t think that will be much of an issue.

I’ll report back after I try it. In the meantime, I’ll file a request for the ability to toggle echo.

not sure if you can use a declare into tcsetattr

BTW, Input does indeed let you use backspace (great!) but still prints ANSI codes (or something like them) when you use the arrows. I want to write an interpreter for these so I still need to use ReadAll with echo off.

@Norman Palardy this looks promising but I don’t see which library defines tcsetattr/tcgetattr on the Mac. Do you happen to know?

not sure - its defined in termios.h

libtermcap looks like the one its in

Thanks. I’ll try this as soon as I can and report back.

No luck, but it feels like I missed something. My code:

const kLib = "libtermcap.dylib"
declare sub tcgetattr lib kLib (file as UInt32, ByRef values as Termios)

dim t as termios
tcgetattr( 0, t )

The result:

dyld: lazy symbol binding failed: Symbol not found: _tcgetattr
  Referenced from: /Users/ktekinay/Desktop/tester.debug/tester.debug
  Expected in: /usr/lib/libncurses.5.4.dylib

dyld: Symbol not found: _tcgetattr
  Referenced from: /Users/ktekinay/Desktop/tester.debug/tester.debug
  Expected in: /usr/lib/libncurses.5.4.dylib

Abort trap: 6

Oh, I just noticed that libtermcap.dylib is a symlink to libncurses.5.4.dylib.

With much thanks to @Norman Palardy, I think I got it.

Starting with the code on this page, I came up with this:

dim s as string 

const kLib = "libncurses.dylib"
const kEcho = &h00000008
const kICanon = &h00000100
const kVmin = 16
const kVtime = 17

soft declare sub tcgetattr lib kLib (file as UInt32, ByRef values as Termios)
soft declare sub tcsetattr lib kLib (file as UInt32, when as UInt32, ByRef values as Termios)

dim origSettings as Termios
tcgetattr( 0, origSettings )

dim newSettings as Termios
newSettings.StringValue( true ) = origSettings.StringValue( true )
newSettings.LocalFlags = newSettings.LocalFlags or kICanon
newSettings.LocalFlags = newSettings.LocalFlags or kEcho
newSettings.LocalFlags = newSettings.LocalFlags xor kEcho
newSettings.LocalFlags = newSettings.LocalFlags xor kICanon
newSettings.ControlChars( kVmin ) = 1
newSettings.ControlChars( kVtime ) = 0

tcsetattr( 0, 0, newSettings )

  dim c as string = StdIn.ReadAll
  if c = &u0A then
  end if
  s = s + c
  StdOut.Write c // Comment to get password processing
print s

tcsetattr( 0, 0, origSettings )

The Termios structure looks like this:

Structure Termios
  InputFlags As UInt32
  OutputFlags As UInt32
  ControlFlags As UInt32
  LocalFlags As UInt32
  ControlChars(19) As Byte
  InputSpeed As UInt32
  OutputSpeed As UInt32
End Structure

If you comment out the indicated line, you will not get anything echoed. Using delete, arrows, etc., still gives you control and ANSI codes that must be interpreted, but now, at least, that’s doable.

Anyone see a pitfall to this that I’ve missed?

Multiple edits to removed the ByRef in tcsetattr, then put it back because it doesn’t work without it.