[Linux] what's going on here then?

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Linux does not have an equivalent to the conio.h getch() and kbhit() which respectively return a single character from the keyboard and return true if a key is waiting; to get this functionality it seems you have to play around with the termio structures... it expects to hand off a string when the return key is pressed, which is less than handy in some circumstances.

 

I'm using code based on this, but this displays my problem: the basic idea is

  • if there's a key waiting
  • grab it
  • output it to the console

but the issue is that the output is delayed until the next key is pressed. Anyone know a way to stop this? It looks as if the putchar is being called when expected, but nothing on the console until the next key is ready - which is less than handy to type with. Any thoughts cheerfully accepted, thanks!

 

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

void changemode(int);
int  kbhit(void);
int main(void)
{
	int ch;
	while (1)
	{
		changemode(1);
		while ( !kbhit() )
		{
			//putchar('.');
		}

		ch = getchar();

		//printf("\nGot %c\n", ch);
		putchar (ch);

		changemode(0);
	}
	return 0;
}

void changemode(int dir)
{
	static struct termios oldt, newt;

	if ( dir == 1 )
	{
		tcgetattr( STDIN_FILENO, &oldt);
		newt = oldt;
		newt.c_lflag &= ~( ICANON | ECHO );
		tcsetattr( STDIN_FILENO, TCSANOW, &newt);
	}
	else
	{
		tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
	}
}

int kbhit (void)
{
	struct timeval tv;
	fd_set rdfs;

	tv.tv_sec = 0;
	tv.tv_usec = 0;

	FD_ZERO(&rdfs);
	FD_SET (STDIN_FILENO, &rdfs);

	select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);
	return FD_ISSET(STDIN_FILENO, &rdfs);
}

Neil

This topic has a solution.
Last Edited: Sat. Sep 19, 2020 - 06:19 AM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
        putchar (ch);
        fflush(stdout);   // make sure output really happens right away

I think there's a bit somewhere to set unbuffered output (setvbuf() ?) permanantly, but I'm not sure that's what you want.

 

It's a vaguely standard hack to flush output when you do an input, but since you're polling kbhit, which would mask this in many cases, but presumably your kbhit function means you don't actually do the input until data exists...

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think it's a termio thing?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This link has some info

 

https://stackoverflow.com/questions/29335758/using-kbhit-and-getch-on-linux

 

also picocom may be worth looking at to see what they did.

 

https://github.com/npat-efault/picocom

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've had success with using timeouts that already exist in the termio structure:

        struct termios tty;
        if ( tcgetattr(fd, &tty) == -1 ) {
                perror("tcgetattr");
                exit(1);
        }
        cfmakeraw(&tty);
        cfsetspeed(&tty, baud);
        tty.c_cc[VMIN]  = 5;            // minimum number of characters
        tty.c_cc[VTIME] = 1;            // or until time-out (0.1s steps)
        if ( tcsetattr(fd, TCSAFLUSH, &tty) == -1 ) {
                perror("tcsetattr");
                exit(1);
        }

 

Mike

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Westfw; fflush was the obvious solution which occurred to me at about six this morning, but I read your answer before I implemented anything so you win the prize!

 

I've vaguely explained what I'm trying to do in the off-topic thread, but to clarify: I have code which emulates a 6502 processor which will eventually move to a Nucleo arm board. At that point, external comms will be via a USB serial link and interrupts; on this x86 version I have set the comms such that writing to a particular address causes an immediate putchar() and reading the same address will load any data there into the accumulator. An IRQ interrupt is triggered whenever there is 'serial' data to read and it's up to the 6502 code to do something with it.

 

Running alongside this is a fairly primitive user interface. The basic use of that is to wait for the 'any' key to be hit and then to call the emulator to execute one 6502 instruction, displaying all the usual disassembly and flag info. It also allows loading of code to execute as a hex file, setting the start from address, and displays memory pages or stack on demand.

 

Two special modes are Trace and Break. The first executes the 6502 code one instruction at a time without waiting for a key press - it stops if either the program counter reaches a preset address, or if a code instruction leaves the program counter unchanged (because the validification code marks a test case fail with a bne $fe but which turns out to be handy anyway). Break does the same thing but without waiting for user keystrokes: instead, the keystrokes are sent to the fake uart address and the IRQ asserted. Which is why I need to display the buffer as it is written rather than waiting for the next input... I actually call the key test code only every 10,000 instructions as it's fairly slow: with the test between each instruction I get a speed equivalent to a 3-4 MHz 6502 but with the less frequent calls it's probably screaming along at 4-500MHz :) And all those years ago I was happy with a 750kHz clock.

 

It's not often I praise Windows over Linux but the conio library is very handy in cases like this.

 

Neil

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Like most things, it's actually quite simple when you know how. 

 

I would look at the 'curses' (plural of 'cursor') library, which was designed, back in the day, as an abstraction layer to handle the hundreds of different character terminal types that might be attached to a Unix system. https://en.wikipedia.org/wiki/Curses_(programming_library)

 

As well as non-blocking keyboard input, it handles cursor movement, arrow and function keys, multiple windows, 'graphics' characters, and, with additional libraries, menus and forms. One of the cleverest parts is the optimisation of screen refreshes, which was important when using a 80x24 terminal over a slow modem link. It also handles terminals (e.g. ANSI) where a single keypress might result in multiple input chars.

 

From the official docs, with examples: https://tldp.org/HOWTO/NCURSES-P...

 

 

Last Edited: Sat. Sep 19, 2020 - 08:45 AM