How to prevent duplicate chars when I press keys on the keyboard - c

I am trying to learn how to prevent the keyboard sending multiple chars to the screen and to scanf under DOS. I am using Turbo-C with inline assembly.
If the characters entered on the keyboard are:
mmmmmmmmyyyyy nnnnnaaaaammmmmmeeeeee iiiiiissss HHHHaaaaiiiimmmm
The characters seen on the console and processed by scanf would be:
my name is Haim
The basic output comes from the code in C which I am not allowed to touch. I must implement eliminate_multiple_press and uneliminate_multiple_presswithout touching the code in between.
The Turbo-C code I've written so far is:
#include <stdio.h>
#include <dos.h>
#include <string.h>
volatile char key;
volatile int i=0;
void interrupt (*Int9save) (void);
void interrupt kill_multiple_press()
{
asm{
MOV AL, 0
MOV AH,1
INT 16h
PUSHF
CALL DWORD PTR Int9save
MOV AX,0
}
asm{
JZ notSet
MOV key, AL
MOV AH, 04H
INT 16H
}
notSet:
//I am not sure what to do from here...............
I also know that it should be related to the zero flag, but what I
wrote so far didn`t effect on multiple characters.
}
void eliminate_multiple_press()
{
Int9save=getvect(9);
setvect(9,kill_multiple_press);
}
void uneliminate_multiple_press()
{
setvect(9,Int9save);
}
void main()
{
char str[10000]="";
clrscr();
eliminate_multiple_press();
printf("Enter your string: ");
scanf("%s",&str);
printf("\n%s",str);
uneliminate_multiple_press();
}
Information I have been given that relate to a solution are the keyboard BIOS routines that can be found at this link:
The problems I'm having are probably related to not understanding what to do at the label notSet. The solution seems to be related to using a buffer and the register AX (especially AL), but I really have no Idea how to make scanf to get the result I need. Does anyone have any ideas how I can complete this code to achieve the desired effect?

There are multiple layers of buffers that may be used by the BIOS, DOS, and the C library (including scanf). When your machine starts up the interrupt vector table is modified to point IRQ1/INT 9h (the external keyboard interrupt) to a BIOS routine to handle characters as they are typed. At the lowest level there is usually a 32 byte6 circular buffer that is maintained in the BIOS Data Area (BDA) to keep track of the characters. You can use the Int 16h BIOS calls1 to interact with this low level keyboard buffer. If you remove characters from the BIOS keyboard buffer at interrupt time then DOS and the C library scanf5 routine will never see them.
Method to Eliminate Duplicate Characters at the BIOS/Interrupt Level
It appears that the exercise is to eliminate all duplicate2 characters entered into scanf3 by intercepting keystrokes via Interrupt 9 (IRQ1) and throwing duplicates away. One idea for a new keyboard interrupt handler to eliminate the duplicates before DOS (and eventually scanf) ever see them:
Keep track of the previous character pressed in a variable
Call the original (saved) Interrupt 9 so that the BIOS updates the keyboard buffer and the keyboard flags as DOS expects them to appear.
Query the keyboard to see if a character is available with Int 16h/AH=1h.The Zero Flag (ZF) will be set if no characters are available and clear if there is one available. This keyboard BIOS call peeks into the beginning of the keyboard buffer without actually removing the next character available.
If a character is available then compare it with the previous character.
If they are different then update the previous character with the current character and exit
If they are the same then use Int 16h/AH=0h to remove the duplicate character from the keyboard buffer and exit
A Turbo-C 3.0x version of the code4:
#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <conio.h>
volatile char key = 0;
void interrupt (*Int9save)(void);
void interrupt kill_multiple_press(void)
{
asm {
PUSHF
CALL DWORD PTR Int9save /* Fake an interrupt call to original handler */
MOV AH, 1 /* Peek at next key in buffer without removing it */
INT 16h
JZ noKey /* If no keystroke then we are finished */
/* If ZF=1 then no key */
CMP AL, [key] /* Compare key to previous key */
JNE updChar /* If characters are not same, update */
/* last character and finish */
/* Last character and current character are same (duplicate)
* Read keystroke from keyboard buffer and throw it away (ignore it)
* When it is thrown away DOS and eventually `scanf` will never see it */
XOR AH, AH /* AH = 0, Read keystroke BIOS Call */
INT 16h /* Read keystroke that has been identified as a */
/* duplicate in keyboard buffer and throw away */
JMP noKey /* We are finished */
}
updChar:
asm {
MOV [key], AL /* Update last character pressed */
}
noKey: /* We are finished */
}
void eliminate_multiple_press()
{
Int9save = getvect(9);
setvect(9, kill_multiple_press);
}
void uneliminate_multiple_press()
{
setvect(9, Int9save);
}
void main()
{
char str[1000];
clrscr();
eliminate_multiple_press();
printf("Enter your string: ");
/* Get a string terminated by a newline. Max 999 chars + newline */
scanf("%999[^\n]s", &str);
printf("\n%s", str);
uneliminate_multiple_press();
}
Notes
1Within the keyboard interrupt handler you want to avoid any keyboard BIOS call that will block waiting for keyboard input. If using Int 16h/AH=0 make sure there is a character available first with Int 16h/AH=1 otherwise Int 16h/AH=0 will block while waiting for another character to arrive.
2Removing duplicate characters is not the same as disabling the keyboard repeat rate.
3Because the duplicates are removed before DOS routines see them (and functions like scanf that rely on DOS), they will never be seen by scanf.
4Some modifications may have to be made to be compatible with versions of Turbo-C other than 3.0x.
5This method only works because scanf will be indirectly making BIOS calls keeping the keyboard buffer clear. This code does't work in all generic cases especially where keystrokes may be buffered by the BIOS. To get around that the keyboard interrupt routine would have to remove all the duplicates in the keyboard buffer not just at the head as this code does.
6Each keystroke takes up 2 bytes of space in the BIOS keyboard buffer (in the BDA). 2 of the 32 bytes are lost because they are used to detect if the keyboard buffer is full or empty. This means the maximum number of keystrokes the BIOS can buffer is 15.

Related

Ending a Loop with EOF (without enter)

i am currently trying to end a while loop with something like this:
#include <stdio.h>
int main()
{
while(getchar() != EOF)
{
if( getchar() == EOF )
break;
}
return 0;
}
When i press CTRL+D on my Ubuntu, it ends the loop immediately. But on Windows i have to press CTRL+Z and then press ENTER to close the loop. Can i get rid of the ENTER on Windows?
The getchar behavior
For linux the EOF char is written with ctrl + d, while on Windows it is written by the console when you press enter after changing an internal status of the CRT library through ctrl + z (this behaviour is kept for retrocompatibility with very old systems). If I'm not wrong it is called soft end of file. I don't think you can bypass it, since the EOF char is actually consumed by your getchar when you press enter, not when you press ctrl + z.
As reported here:
In Microsoft's DOS and Windows (and in CP/M and many DEC operating systems), reading from the terminal will never produce an EOF. Instead, programs recognize that the source is a terminal (or other "character device") and interpret a given reserved character or sequence as an end-of-file indicator; most commonly this is an ASCII Control-Z, code 26. Some MS-DOS programs, including parts of the Microsoft MS-DOS shell (COMMAND.COM) and operating-system utility programs (such as EDLIN), treat a Control-Z in a text file as marking the end of meaningful data, and/or append a Control-Z to the end when writing a text file. This was done for two reasons:
Backward compatibility with CP/M. The CP/M file system only recorded the lengths of files in multiples of 128-byte "records", so by convention a Control-Z character was used to mark the end of meaningful data if it ended in the middle of a record. The MS-DOS filesystem has always recorded the exact byte-length of files, so this was never necessary on MS-DOS.
It allows programs to use the same code to read input from both a terminal and a text file.
Other information are also reported here:
Some modern text file formats (e.g. CSV-1203[6]) still recommend a trailing EOF character to be appended as the last character in the file. However, typing Control+Z does not embed an EOF character into a file in either MS-DOS or Microsoft Windows, nor do the APIs of those systems use the character to denote the actual end of a file.
Some programming languages (e.g. Visual Basic) will not read past a "soft" EOF when using the built-in text file reading primitives (INPUT, LINE INPUT etc.), and alternate methods must be adopted, e.g. opening the file in binary mode or using the File System Object to progress beyond it.
Character 26 was used to mark "End of file" even if the ASCII calls it Substitute, and has other characters for this.
If you modify your code like that:
#include <stdio.h>
int main() {
while(1) {
char c = getchar();
printf("%d\n", c);
if (c == EOF) // tried with also -1 and 26
break;
}
return 0;
}
and you test it, on Windows you will see that the EOF (-1) it is not written in console until you press enter. Beore of that a ^Z is printed by the terminal emulator (I suspect). From my test, this behavior is repeated if:
you compile using the Microsoft Compiler
you compile using GCC
you run the compiled code in CMD window
you run the compiled code in bash emulator in windows
Update using Windows Console API
Following the suggestion of #eryksun, I successfully written a (ridiculously complex for what it can do) code for Windows that changes the behavior of conhost to actually get the "exit when pressing ctrl + d". It does not handle everything, it is only an example. IMHO, this is something to avoid as much as possible, since the portability is less than 0. Also, to actually handle correctly other input cases a lot more code should be written, since this stuff detaches the stdin from the console and you have to handle it by yourself.
The methods works more or less as follows:
get the current handler for the standard input
create an array of input records, a structure that contains information about what happens in the conhost window (keyboard, mouse, resize, etc.)
read what happens in the window (it can handle the number of events)
iterate over the event vector to handle the keyboard event and intercept the required EOF (that is a 4, from what I've tested) for exiting, or prints any other ascii character.
This is the code:
#include <windows.h>
#include <stdio.h>
#define Kev input_buffer[i].Event.KeyEvent // a shortcut
int main(void) {
HANDLE h_std_in; // Handler for the stdin
DWORD read_count, // number of events intercepted by ReadConsoleInput
i; // iterator
INPUT_RECORD input_buffer[128]; // Vector of events
h_std_in = GetStdHandle( // Get the stdin handler
STD_INPUT_HANDLE // enumerator for stdin. Others exist for stdout and stderr
);
while(1) {
ReadConsoleInput( // Read the input from the handler
h_std_in, // our handler
input_buffer, // the vector in which events will be saved
128, // the dimension of the vector
&read_count); // the number of events captured and saved (always < 128 in this case)
for (i = 0; i < read_count; i++) { // and here we iterate from 0 to read_count
switch(input_buffer[i].EventType) { // let's check the type of event
case KEY_EVENT: // to intercept the keyboard ones
if (Kev.bKeyDown) { // and refine only on key pressed (avoid a second event for key released)
// Intercepts CTRL + D
if (Kev.uChar.AsciiChar != 4)
printf("%c", Kev.uChar.AsciiChar);
else
return 0;
}
break;
default:
break;
}
}
}
return 0;
}
while(getchar() != EOF)
{
if( getchar() == EOF )
break;
}
return 0;
Here it is inconsistent.
If getchar() != EOF it will enter the loop, otherwise (if getchar() == EOF) it will not enter the loop. So, there is no reason to check getchar() == EOF inside the loop.
On the other hand, you call getchar() 2 times, you wait to enter 2 characters instead of only 1.
What did you try to do ?

Loop until user inputs an Integer, but skip the read in if no integer exists on the terminal

Im trying to create an application that will count pulses from an Oscilloscope, my problem is this:
I need a loop to run constantly, until a user input is entered. However I dont want getch() to be called unless there is an input on the terminal ready to be read. How would I go about checking for a character or integer existing on the terminal?
If you're coding for UNIX, you'll need to use either poll(2) or select(2).
Since you mention the non-standard getch you might also have kbhit which tests if there is input waiting. But don't slug your oscilloscope with that every loop: you can ease the flow by checking occasionally.
#include <conio.h>
#include <stdio.h>
#define POLLMASK 0xFFFF
int main(void){
int poll = 0;
int ch = 0;
while(ch != 27) {
// ... oscilloscope details
if ((poll++ & POLLMASK) == 0 && _kbhit()) {
ch = _getch();
// ... do something with input
}
}
return 0;
}
Use scanf() . It's standard, it already waits for a character in the buffer, and it's pretty much universal.
EDIT:
In the case of an unnamed OS, this is simply not to be done. Standard C has no way of reading raw data from a terminal and guaranteeing verbatim, unformatted results. It's entirely up to your terminal handler.
However, if your microcontroller has some sort of serial library, I would suggest doing something like this:
if characters in buffer is greater than 0, and the character just read is not a terminating character, report it. Otherwise, keep waiting for a character in the buffer.
until a user input is entered. this sentence indicates the user will use the keyboard eventually, therefore, you can track keyboard callback event to track the user's input. There are several libraries that provide you with such keyboard events (e.g. GLUT, SDL)

writing to stdin and then moving the stream back 1 position

I have made a little program where I can move the cursor up,down,left and right.
now the last thing I want to add is the ability for the cursor to jump to the next or previous if it gets to the edge of the screen.
since I already implemented all kinds of checks to make sure I can move up and down within the key_up and key_down input handle parts I would like to 'borrow' these. I could of course rewrite them and reuse them in the key_left and key_right scenarios.
However it would be way shorter if I could just write to stdin and 'fake' user input as if key_up or key_down was pressed, and only have to change the x-position of the cursor.
so what I want something like this
putc(KEY_UP,stdin);fseek(stdin,-1, SEEK_CUR);
so on the next getch() it retrieves the character I put to stdin with putc!
I use ncurses for the entire program interface!
on Filipe suggestion I tried to do something with ungetc(), but it doesn't have the behavior I want.
here is a small test program, in theory it should print out infinite p's after the first character but it doesn't:
#include <stdio.h>
#include <stdlib.h>
#include <ncurses.h>
int main () {
int input,i=0;
initscr();
start_color();
keypad(stdscr, TRUE);
raw();
nonl();
noecho();
do {
input=getch();
if (i==24) {i=0;erase();}
mvprintw(i,0,"%c --- %i",input,input);
refresh();
if (ungetc((unsigned char)'p', stdin)==EOF) {
printw(" ERROR UNGETC");
}
else {printw(" SUCCES UNGETC");}
++i;
} while (input!='q');
endwin();
exit(2);
}
Since you're using ncurses, you're looking for ungetch(). From the manpage:
The ungetch routine places ch back onto the input queue to be returned
by the next call to wgetch. There is just one input queue for all
windows.
The function prototype:
int ungetch(int ch);
It returns the integer ERR upon failure and an integer value other than ERR (OK in the case of ungetch()) upon successful completion.
For future reference only
If anyone reading this answer wants a simple way to push back characters onto a file stream, ungetc() is the correct approach. ungetch() is appropriate only for anyone using ncurses.
ungetc() pushes back one character into a given file stream:
int ungetc(int c, FILE *stream);
From the manpage:
ungetc() pushes c back to stream, cast to unsigned char, where it is
available for subsequent read operations. Pushed-back characters will
be returned in reverse order; only one pushback is guaranteed.
You will want to call it with stdin, as in:
ungetc((unsigned char) KEY_UP, stdin);
It returns c on success, EOF on error. Bear in mind that it only guarantees one push back character; you can't call ungetc() twice and then expect that calling getch() twice gives you back the last 2 pushed characters.

How to print strings using bios interrupts

I am working on an assigment on creating an operating system using some assembly functions and 16 bit C compiler. My task is to print strings on screen using 0x10 interrupt. Since interrupts can be called in assembly file, I have been provided with an assembly file which contains a function called interrupt which takes five arguments : the interrupt number, and the interrupt parameters passed in the AX, BX, CX, and DX.
For example, to print 'Q' with the provided function, I need to write like this:
char al = 'Q'
char ah = 0xE
int ax = ah*256+al;
interrupt(0x10,ax,0,0,0);
OR, simply:
interrupt(0x10,0xE*256+'Q',0,0,0);
in a C program called kernel.c
My task is to write a function printString(char *chars) in C which takes a string and prints it on screen using the discussed assembly function.
I have done it this way:
void printString(char * chars){
int i = 0;
int l = length(chars);
for(; i < l; i++){
interrupt(0x10,0xE*256+chars[i],0,0,0);
}
}
but it prints the string multiple times instead of printing one time.
when I try to print "Hello World", it's printed 11 times, because it contains 11 characters, same is the case with other strings.
I think you need to look for a null character to terminate the read. I've noticed the assembly file does some weird stuff with the character buffers too. I even had multiple characters print when I called the interrupt function directly from main().
Adding the line: while(1); keeps main() from returning. The boot loader executing multiple instances of main() is what causes the repeated output.

determing if a user pressed tab [duplicate]

This question already has answers here:
How to detect that arrow key is pressed using C under Linux or Solaris?
(3 answers)
Closed 8 years ago.
So I am writing a program, and of the the tasks is to determine if a user has pressed tab. So when he presses tab, I should print something to the console(or do tab completition etc). My problem is how do I do it without the user pressing enter. I tried looking into ncurses but I couldn't find a simple example that would teach me how to do it with tab.
Edit:
Using Linux
You can act on input using ncurses and the getch() function. It's going to return you an int value for the key pressed, you can check for a tab via looking to see if the return was 9. This code will loop displaying what was pressed until it was a tab then it exits.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ncurses.h>
int main() {
int c;
initscr(); /* Start curses mode */
cbreak();
noecho();
while(9 != (c = getch())) {
printw("%c\n", c);
if(halfdelay(5) != ERR) { /* getch function waits 5 tenths of a second */
while(getch() == c)
if(halfdelay(1) == ERR) /* getch function waits 1 tenth of a second */
break;
}
printw("Got a %d\n", c);
cbreak();
}
endwin();
return 0;
}
Technically this is not a C language question but a matter of operating system or runtime environment. On POSIX systems you must set, at least, your terminal in non-canonical mode.
The canonical mode buffers keyboard inputs to process them further if needed (for example, this lets you erase chars before your application see them).
There is many ways to switch to non-canonical mode. Of course you can use many different libraries ncurses, etc. But the trick behind is a set of system calls called termios. What you have to do is to read current attributes of the POSIX terminal and modify them accordingly to your needs. For example :
struct termios old, new;
/* read terminal attributes */
tcgetattr(STDIN_FILENO,&old);
/* get old settings */
new=old;
/* modify the current settings (common: switch CANON and ECHO) */
new.c_lflag &=(~ICANON & ~ECHO);
/* push the settings on the terminal */
tcsetattr(STDIN_FILENO,TCSANOW,&new);
do_what_you_want_and_read_every_pressed_char();
/* ok, restore initial behavior */
tcsetattr(STDIN_FILENO,TCSANOW,&old);

Resources