hey, been working on a program and I had a need for a password line (btw I am using linux w/ gcc compiler). while I was making it I checked the net and found there's no getch() for linux, so I frankensteined one from several sources and got it to work. Only problem is, it doesn't register backspace ('\b'), so people can't correct mistakes. I tried using a different key instead of backspace, like \t for example, it worked as it should, getchar() registered it, it just doesn't seem to like \b. here's the code:

#include <cstring>
#include <iostream>
#include <unistd.h>
using namespace std;

#ifndef KBHITh
#define KBHITh

#include <termios.h>

class keyboard
{
public:

	keyboard();
	~keyboard();

private:

	struct termios initial_settings, new_settings;
};

#endif 

keyboard::keyboard()
{
	tcgetattr(0,&initial_settings);
	new_settings = initial_settings;
	new_settings.c_lflag &= ~ICANON;
	new_settings.c_lflag &= ~ECHO;
	new_settings.c_lflag &= ~ISIG;
	new_settings.c_cc[VMIN] = 1;
	new_settings.c_cc[VTIME] = 0;
	tcsetattr(0, TCSANOW, &new_settings);
}

keyboard::~keyboard()
{
	tcsetattr(0, TCSANOW, &initial_settings);
}

int main()
{
	keyboard my_board; //essentially removes line buffer
	string pass="";
	char c=' ';
	cout << "Testing passwords, enter a string to passwordize\n";
	
	while(c != '\n')
	{
		c = getchar();
		if(c != '\n')
		{
			if(c == '\b') //doesn't like this line
			{
				pass=pass.substr(0,pass.size()-1);
				cout << "\010 \010" << flush;
			}
			else
			{
				pass.push_back(c);
				cout << "*" << flush;
			}
		}
	}

        my_board.~keyboard(); //reverts to normal line buffering
	cout << "\nYou entered: \"" << pass << "\"" << endl;
		
	return 0;
}

I've heard rumors of getchar not being able to read backspaces, but I wasn't sure. Also, I have no doubt there are better ways to emulate getch(), if you know of one please point me in the right direction. Any help would be appreciated.

~J

Dani AI

Generated

Nice find on keypad(stdscr, TRUE), . One point to clear up: in raw/noncanonical mode the terminal is not eating backspace; it is passing you whatever byte the terminal has configured as the erase character. On many Linux setups that is DEL (0x7F, shown as ^?), not BS (0x08, ^H). That is why '\b' was not matching. You can either accept both 0x7F and 0x08, or (better) read the TTY’s configured erase byte once and compare against that. Also consider accepting both LF and CR for Enter, since mappings like ICRNL may differ.

Here is a minimal, non-curses input handler you can drop into your loop after you have disabled ICANON and ECHO. It honors the terminal’s erase setting and avoids surprises across terminals:

#include <termios.h>
#include <unistd.h>
#include <cstdio>
#include <string>

unsigned char get_erase_char() {
    termios t{};
    tcgetattr(STDIN_FILENO, &t);
    return t.c_cc[VERASE];
}

// before the loop:
const unsigned char ERASE = get_erase_char();

// inside the loop:
int ch = getchar();
if (ch == '\n' || ch == '\r') break;
if ((unsigned char)ch == ERASE || ch == 127 || ch == 8) {
    if (!pass.empty()) { pass.pop_back(); fputs("\b \b", stdout); fflush(stdout); }
} else {
    pass.push_back((char)ch); putchar('*'); fflush(stdout);
}

A few small gotchas the thread has not called out yet:

  • You usually do not need to disable ISIG; leaving signals on keeps Ctrl-C/Ctrl-Z working.
  • Do not call the destructor explicitly (my_board.~keyboard();). Let RAII restore the TTY when the object goes out of scope.
  • If you stick with ncurses (as suggested), you can also query the terminal’s erase byte with erasechar() and compare against that when mixing curses with plain stdio. Mixing cout and curses can flicker; prefer one I/O path per screen.

Recommended Answers

All 3 Replies

>>Also, I have no doubt there are better ways to emulate getch(), if you know of one please point me in the right direction.

Its called curses.

hmmm... yes that does the job (and may look a little neater), but it clears the screen and only outputs things from the function until the program ends, that is not something I want to happen when I actually implement the function in the real program, unless there is an option for it not to do that. It also seems to again not register '\b', so I still have my original problem. code follows.

#include <cstring>
#include <iostream>
#include <ncurses.h>
using namespace std;

int main()
{
	initscr();
	noecho();
	string pass="";
	char c=' ';
	cout << "Testing password stuff, enter a string to passwordize1\n" << flush;
	
	while(c != '\n')
	{
		c = getch();
		if(c != '\n')
		{
			if(c == '\b')
			{
				pass=pass.substr(0,pass.size()-1);
				cout << "\010 \010" << flush;
			}
			else
			{
				pass.push_back(c);
				cout << "*" << flush;
			}
		}
	}

	endwin();
	cout << "\nYou entered: \"" << pass << "\"" << endl;
		
	return 0;
}

I got it to read backspaces, keypad() does that trick. I still wish there was another way to do it while still inside the terminal. That just confirms the things I heard that the terminal intercepts the backspace before it can get to the program (not sure why, but it seems that way). Anywho, thanks for the nudge Ancient Dragon, I think I can still make it look/do the things I would like. For sake of completeness on anyone following this thread, here's the finished code:

#include <cstring>
#include <iostream>
#include <ncurses.h>
using namespace std;

int main()
{
	initscr();
	noecho();
	keypad(stdscr, TRUE);
	string pass="";
	int c=0;
	cout << "Testing password stuff, enter a string to passwordize1\n" << flush;
	
	while(c != '\n')
	{
		c = getch();
		if(c != '\n')
		{
			if(c == KEY_BACKSPACE)
			{
				pass=pass.substr(0,pass.size()-1);
				cout << "\010 \010" << flush;
			}
			else
			{
				pass.push_back(c);
				cout << "*" << flush;
			}
		}
	}

	endwin();
	cout << "\nYou entered: \"" << pass << "\"" << endl;
		
	return 0;
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.