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.
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.
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.
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.
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.
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 )
do
dim c as string = StdIn.ReadAll
if c = &u0A then
exit
end if
s = s + c
StdOut.Write c // Comment to get password processing
loop
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.