Yoni, you have two good answers, but for completeness, I'll provide the remaining information you had questions with in the comments.
First when taking any input your want to display and allow minimal user editing of, you will need to put your keyboard in non-cannonical mode so that each keypress is available to your program as the key is typed -- no need to wait for the user to press Enter. You handle this with tcgetattr
(terminal get attributes) and tcsetattr
(terminal set attributes). *non-cannonical mode is what windows getch()
provides.
Essentially you set up your read-loop to read with getchar
or fgetc
(if you want to be able to read the values from stdin
or a file.) You control the read loop with:
#define MAXPW 32 /* constant for max input length */
int main (int argc, char **argv) {
int c,
idx = 0; /* buf index */
char pw[MAXPW] = "", /* buf for passwd */
mask = argc > 1 ? *argv[1] : 0; /* mask off by default */
FILE *fp = stdin;
...
/* read chars from fp, mask w/mask char */
while ((idx + 1 < MAXPW && (c = fgetc (fp)) != '\n' && c != EOF) ||
(idx == MAXPW - 1 && c == 127))
{
Note carefully, you are reading a character while (1) (space_remains AND c
isn't '\n'
or EOF
) OR (2) (space_remains AND backspace_key_pressed)
In that even you handle the two cases (1) was it a normal char - add it; or (2) was it a backspace character, then backup, overwrite char with space
and backup again, e.g.
if (c != 127) { /* not the backspace characters */
if (' ' - 1 < mask && mask < 127) /* if mask valid ASCII */
fputc (mask, stdout); /* output mask char */
else
fputc (c, stdout); /* output normal char */
pw[idx++] = c; /* store char, adv index */
}
else if (idx > 0) { /* handle backspace (del) */
fputc (0x8, stdout); /* backup */
fputc (' ', stdout); /* overwrite with space */
fputc (0x8, stdout); /* backup again */
pw[--idx] = 0; /* nul-termiante after current */
}
note: if your mask
character is a printable character, then the mask
character is output, e.g.
enter passwd: ********
If the mast is not printable (default is nul
), then the text is output. You can set the mask character as the 1st argument to the program (in quotes), e.g.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <termios.h>
#include <errno.h> /* for errno */
#include <unistd.h> /* for EINTR */
#define MAXPW 32 /* constant for max input length */
int main (int argc, char **argv) {
int c,
idx = 0; /* buf index */
char pw[MAXPW] = "", /* buf for passwd */
mask = argc > 1 ? *argv[1] : 0; /* mask off by default */
FILE *fp = stdin;
struct termios old_kbd_mode; /* orig keyboard settings */
struct termios new_kbd_mode;
if (tcgetattr (0, &old_kbd_mode)) { /* save orig settings */
fprintf (stderr, "%s() error: tcgetattr failed.\n", __func__);
return -1;
} /* copy old to new */
memcpy (&new_kbd_mode, &old_kbd_mode, sizeof(struct termios));
/* put keyboard in non-canonical/no echo mode */
new_kbd_mode.c_lflag &= ~(ICANON | ECHO); /* new kbd flags */
new_kbd_mode.c_cc[VTIME] = 0;
new_kbd_mode.c_cc[VMIN] = 1;
if (tcsetattr (0, TCSANOW, &new_kbd_mode)) {
fputs ("error: tcsetattr failed.\n", stderr);
return -1;
}
fputs ("enter passwd : ", stdout); /* set passwd prompt */
/* read chars from fp, mask w/mask char */
while ((idx + 1 < MAXPW && (c = fgetc (fp)) != '\n' && c != EOF) ||
(idx == MAXPW - 1 && c == 127))
{
if (c != 127) { /* not the backspace characters */
if (' ' - 1 < mask && mask < 127) /* if mask valid ASCII */
fputc (mask, stdout); /* output mask char */
else
fputc (c, stdout); /* output normal char */
pw[idx++] = c; /* store char, adv index */
}
else if (idx > 0) { /* handle backspace (del) */
fputc (0x8, stdout); /* backup */
fputc (' ', stdout); /* overwrite with space */
fputc (0x8, stdout); /* backup again */
pw[--idx] = 0; /* nul-termiante after current */
}
}
pw[idx] = 0; /* null-terminate final string */
/* restore original keyboard mode */
if (tcsetattr (0, TCSANOW, &old_kbd_mode)) {
fputs ("error: tcsetattr failed.\n", stderr);
return -1;
}
printf ("\nstored passwd: %s\n", pw);
}
An Editable Input
Running the program without a mask, say the user enters:
$ ./bin/backspace
enter passwd : my_password_is_bad
(the user thinks about and says "oh, that's not good" and can now hit the backspace key 3 times which leaver her looking at:
$ ./bin/backspace
enter passwd : my_password_is_
Now she completes her input:
$ ./bin/backspace
enter passwd : my_password_is_good
stored passwd: my_password_is_good
The operations work exactly the same with the mask character displayed. With all mask characters displayed and the user forgot what she had entered, she could simply backspace over all characters show (and keep smashing the backspace key if she feels like it, and then proceed again to enter a correct password (name, whatever). Example with a mask
char of '*'
, e.g.
$ ./bin/backspace '*'
enter passwd : *******************
stored passwd: my_password_is_good
It's a handy way to provide minimal edit capabilities to the user in some circumstances. If you don't need to mask the user input, then you can completely do away with changing the keyboard mode.
Look things over and let me know if you have questions.