Discussion:
Getting a keypress
(too old to reply)
Janis Papanagnou
2023-12-19 09:46:02 UTC
Permalink
I'm trying to get some pressed keys control a piece of software.
And I'm using ANSI escapes to control terminal screen output.
Both works nicely in separate tests, but assembled the former
seems to affect the latter. So I am looking for hints/caveats on
what I am possibly doing wrong.

The "poll pressed key" function is basically derived from Steven's
APUE book (tty_cbreak). The main functional parts are the following
settings...

fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);

and

struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);

Given that there are bazillions of flags to set or clear; is
there anything I should add or omit? (Or something else I should
have an eye on?)

Janis
M***@dastardlyhq.com
2023-12-19 10:04:10 UTC
Permalink
On Tue, 19 Dec 2023 10:46:02 +0100
Post by Janis Papanagnou
I'm trying to get some pressed keys control a piece of software.
And I'm using ANSI escapes to control terminal screen output.
Make sure your terminal is 100% ANSI compatible. Not all are - some codes will
work, others won't and others can cause odd behaviour.
Post by Janis Papanagnou
Both works nicely in separate tests, but assembled the former
seems to affect the latter. So I am looking for hints/caveats on
what I am possibly doing wrong.
The "poll pressed key" function is basically derived from Steven's
APUE book (tty_cbreak). The main functional parts are the following
settings...
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
I've never had to use that, try removing it to see if it helps.
Post by Janis Papanagnou
and
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Instead of TCSAFLUSH try TCSANOW and make sure you check the return code
for -1.
Janis Papanagnou
2023-12-19 16:44:39 UTC
Permalink
Post by M***@dastardlyhq.com
On Tue, 19 Dec 2023 10:46:02 +0100
Post by Janis Papanagnou
I'm trying to get some pressed keys control a piece of software.
And I'm using ANSI escapes to control terminal screen output.
Make sure your terminal is 100% ANSI compatible. Not all are - some codes will
work, others won't and others can cause odd behaviour.
Yes, as said, individually the two features work flawlessly.
I am using ANSI escapes on a regular basis without problems.
And I avoid (if possible) escapes that are marked to be (in
any way) "specific".
Post by M***@dastardlyhq.com
Post by Janis Papanagnou
Both works nicely in separate tests, but assembled the former
seems to affect the latter. So I am looking for hints/caveats on
what I am possibly doing wrong.
The "poll pressed key" function is basically derived from Steven's
APUE book (tty_cbreak). The main functional parts are the following
settings...
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
I've never had to use that, try removing it to see if it helps.
I would think that non-blocking read(2) is what I need to
_poll_ the status; am I missing something here?
Post by M***@dastardlyhq.com
Post by Janis Papanagnou
and
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Instead of TCSAFLUSH try TCSANOW and make sure you check the return code
for -1.
All system calls in my programs check for the respective exit
codes. I just omitted all that ballast for posting terseness
and focused on the functional parts only.
Post by M***@dastardlyhq.com
Also are you sure fd is stdin? Perhaps use STDIN_FILENO.
Yes, fd is a parameter from my poll function that is defined
as STDIN_FILENO.

Richard Stevens, where I took the basic pieces of code from,
was very painstaking with his code samples. :-)

Thanks.

Janis
Janis Papanagnou
2023-12-19 19:02:46 UTC
Permalink
Post by M***@dastardlyhq.com
On Tue, 19 Dec 2023 10:46:02 +0100
Post by Janis Papanagnou
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
I've never had to use that, try removing it to see if it helps.
I am confused. - I removed that fcntl() call completely and the output
wasn't affected any more in the observed negative way. - Why that?
Don't I need to use non-blocking I/O for a poll?
(Or is maybe buf.c_cc[VMIN] = 1 serving the same purpose?)
But why has a non-blocking input definition an effect on _output_?
You see me puzzled.
Post by M***@dastardlyhq.com
Post by Janis Papanagnou
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Instead of TCSAFLUSH try TCSANOW [...]
I tried this change, but it had no visible effect (neither to the good
nor to the bad).


So I'll try to continue my code without the above fcntl() call.
Thanks.

Explanations and more suggestions still welcome.

Janis
M***@dastardlyhq.com
2023-12-20 09:10:00 UTC
Permalink
On Tue, 19 Dec 2023 20:02:46 +0100
Post by Janis Papanagnou
Post by M***@dastardlyhq.com
On Tue, 19 Dec 2023 10:46:02 +0100
Post by Janis Papanagnou
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
I've never had to use that, try removing it to see if it helps.
I am confused. - I removed that fcntl() call completely and the output
wasn't affected any more in the observed negative way. - Why that?
Don't I need to use non-blocking I/O for a poll?
(Or is maybe buf.c_cc[VMIN] = 1 serving the same purpose?)
But why has a non-blocking input definition an effect on _output_?
You see me puzzled.
Post by M***@dastardlyhq.com
Post by Janis Papanagnou
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Instead of TCSAFLUSH try TCSANOW [...]
I tried this change, but it had no visible effect (neither to the good
nor to the bad).
Sorry I can't help any more. The above code has always worked for me.
Janis Papanagnou
2023-12-20 11:32:42 UTC
Permalink
Post by M***@dastardlyhq.com
On Tue, 19 Dec 2023 20:02:46 +0100
Post by Janis Papanagnou
Post by M***@dastardlyhq.com
On Tue, 19 Dec 2023 10:46:02 +0100
Post by Janis Papanagnou
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
I've never had to use that, try removing it to see if it helps.
I am confused. - I removed that fcntl() call completely and the output
wasn't affected any more in the observed negative way. - Why that?
Don't I need to use non-blocking I/O for a poll?
(Or is maybe buf.c_cc[VMIN] = 1 serving the same purpose?)
But why has a non-blocking input definition an effect on _output_?
You see me puzzled.
Post by M***@dastardlyhq.com
Post by Janis Papanagnou
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Instead of TCSAFLUSH try TCSANOW [...]
I tried this change, but it had no visible effect (neither to the good
nor to the bad).
Sorry I can't help any more. The above code has always worked for me.
Just to clarify; your suggestion to remove the fcntl() call already
solved the issue! [*]

Your proposal also works with TCSAFLUSH, so TCSANOW at least doesn't
seem necessary. Since you said I should "try" TCSANOW - which seemed
to imply no strict reason to use it - I also wanted to give feedback
on that part.

So thanks again. I don't know *why* it works (or rather, why it didn't
work before), but now [without the fcntl()] it does. :-)

Thanks also to all the other posters who helped clarifying the issue,
for the suggestions, and discussions!

Janis

[*] It would certainly have been good if I'd understand why the fcntl()
on stdin was in the way, and why it affected output. - But okay, that's
just my honest desire to try to understand what I'm doing. ;-)
Kaz Kylheku
2023-12-20 18:10:52 UTC
Permalink
Post by Janis Papanagnou
Your proposal also works with TCSAFLUSH, so TCSANOW at least doesn't
seem necessary. Since you said I should "try" TCSANOW - which seemed
to imply no strict reason to use it - I also wanted to give feedback
on that part.
TCSAFLUSH is evil; if your user is a fast typist who can enter keyboard
input before your program has initialized, they will find that their
buffered keystrokes were lost. You won't easily see it in a small
program whose executable and libraries are cache hot, and which doesn't
read a lot of startup files and such.

(Vim does this annoying flush thing. I posted about it to the mailing
list many years ago and was rudely dismissed.)

If you only care about changing input parameters, you want TCSANOW.
If any of your options affect output, you may want any previously
written, buffered output (not just by your program but anything that
came before!) to be drained before the changes take effect, for
which there is TCSADRAIN.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
Janis Papanagnou
2023-12-21 09:43:03 UTC
Permalink
Post by Kaz Kylheku
TCSAFLUSH is evil; if your user is a fast typist who can enter keyboard
input before your program has initialized, they will find that their
buffered keystrokes were lost. You won't easily see it in a small
program whose executable and libraries are cache hot, and which doesn't
read a lot of startup files and such.
This may actually be a good property in my case; I want to use only
the keystrokes that are typed after initialization. (It's also just
a "small program"; a game that prints some hints and waits to start
after a keystroke, and then gets controlled by further keystrokes.)
Post by Kaz Kylheku
[...]
If you only care about changing input parameters, you want TCSANOW.
If any of your options affect output, you may want any previously
written, buffered output (not just by your program but anything that
came before!) to be drained before the changes take effect, for
which there is TCSADRAIN.
Okay, thanks.

Janis
Julieta Shem
2023-12-21 18:44:00 UTC
Permalink
Post by Kaz Kylheku
Post by Janis Papanagnou
Your proposal also works with TCSAFLUSH, so TCSANOW at least doesn't
seem necessary. Since you said I should "try" TCSANOW - which seemed
to imply no strict reason to use it - I also wanted to give feedback
on that part.
TCSAFLUSH is evil; if your user is a fast typist who can enter keyboard
input before your program has initialized, they will find that their
buffered keystrokes were lost. You won't easily see it in a small
program whose executable and libraries are cache hot, and which doesn't
read a lot of startup files and such.
(Vim does this annoying flush thing. I posted about it to the mailing
list many years ago and was rudely dismissed.)
Lol. Do you have a message-id or something? I'd love to have a look.
Kaz Kylheku
2023-12-21 22:07:12 UTC
Permalink
Post by Julieta Shem
Post by Kaz Kylheku
Post by Janis Papanagnou
Your proposal also works with TCSAFLUSH, so TCSANOW at least doesn't
seem necessary. Since you said I should "try" TCSANOW - which seemed
to imply no strict reason to use it - I also wanted to give feedback
on that part.
TCSAFLUSH is evil; if your user is a fast typist who can enter keyboard
input before your program has initialized, they will find that their
buffered keystrokes were lost. You won't easily see it in a small
program whose executable and libraries are cache hot, and which doesn't
read a lot of startup files and such.
(Vim does this annoying flush thing. I posted about it to the mailing
list many years ago and was rudely dismissed.)
Lol. Do you have a message-id or something? I'd love to have a look.
According to my own mail archives, I first posted about it to the
***@vim.org mailing list on 2012-03-27 with the subject
"Typeahead character loss when running Vim.".

I think I got no reply, so I pinged the topic, sme subject line with Re:
prepended, on 2014-02-18, almost two years later.

I don't see any of the replies in my archives, though I subscribed
to the list at the time. I might have looked at the archive for
the reply.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
Julieta Shem
2023-12-22 05:33:51 UTC
Permalink
Post by Kaz Kylheku
Post by Julieta Shem
Post by Kaz Kylheku
Post by Janis Papanagnou
Your proposal also works with TCSAFLUSH, so TCSANOW at least doesn't
seem necessary. Since you said I should "try" TCSANOW - which seemed
to imply no strict reason to use it - I also wanted to give feedback
on that part.
TCSAFLUSH is evil; if your user is a fast typist who can enter keyboard
input before your program has initialized, they will find that their
buffered keystrokes were lost. You won't easily see it in a small
program whose executable and libraries are cache hot, and which doesn't
read a lot of startup files and such.
(Vim does this annoying flush thing. I posted about it to the mailing
list many years ago and was rudely dismissed.)
Lol. Do you have a message-id or something? I'd love to have a look.
According to my own mail archives, I first posted about it to the
"Typeahead character loss when running Vim.".
prepended, on 2014-02-18, almost two years later.
I don't see any of the replies in my archives, though I subscribed
to the list at the time. I might have looked at the archive for
the reply.
You got two replies in 2014. Have a look at

https://groups.google.com/g/vim_use/c/XX6SoxHAzhI

That's how nice it is to have a decent USENET archive. Soon we won't
count on this one, but we won't miss the spam either. Speaking of
Google Groups, it is itself the archive pointed out by the vim mail list
homepage at https://www.vim.org/maillist.php. Now, when I went to the
archive, I could not find your message at all, but duckduckgo.com
readily located at Google Groups. (It's nice to have an archive, even
if it's not searchable.)

What e-mail client were you using then? It seems to have inserted line
breaks not where you really wanted.

(*) Your message

--8<---------------cut here---------------start------------->8---
Hello?

I asked this nearly two years ago; doesn't anyone else have this
problem?

Is it fixed in newer versions of Vim?

On an Ubuntu 11.04 which has a dated version of Vim, if I do this:

$ vim some_file

and upon hitting enter, immediately type, "io", sometimes Vim ends up in
command mode as if nothing was typed, then sometimes it ends up in an
insert on the same line, and the character o has been entered, as
expected.
Most of the time, though, one of the other four possibilities happens,
based on which of the two characters is dropped, or both: it's in
command
mode, insert mode on the same line, or insert mdoe with a new line
having
been o)pened.
Post by Kaz Kylheku
I'm using Vim 7.3 on Ubuntu 11.x.
When I run $ vim file.txt from the shell, and start typing a command
like /quicksort,
some of the characters end up missing. It might end up /quickt.
I type very fast and can queue up quite a bunch of keystrokes, which I
expect to be executed properly.
This is especially annoying when the character loss turns a harmless
positioning
command into an edit which then has to be undone.
Looks like Vim may be discarding some characters (or telling the TTY
driver to do that).
--
Memorize Japanese Characters with Tankan.
--8<---------------cut here---------------end--------------->8---

(*) Reply by Gary Johnson

--8<---------------cut here---------------start------------->8---
I had this problem years ago at a different company when I had an X
server running on my desktop machine and I ran vim on a server in
the back room. I think the terminal was running on the desktop
machine.

As I recall, the problem was due to the terminal response sequence
arriving late so that vim thought all or part of it was coming from
the keyboard. Also as I recall, my solution was to set t_RV to an
empty string and set 'ttymouse' to "xterm2" since vim was then
unable to determine the value of 'ttymouse' from the terminal
response.

To find out more about t_RV,

:helpgrep t_RV

I don't know if this has been fixed or not. I haven't seen the
problem since changing companies and running everything on the
back-room servers with NoMachine on my Windows desktop. I did see a
problem for a while with the terminal response sequence interfering
with vimdiffs, but I haven't seen that for maybe a year.

HTH,
Gary
--8<---------------cut here---------------end--------------->8---

(*) Reply by Tony Mechelynck

--8<---------------cut here---------------start------------->8---
Post by Kaz Kylheku
Hello?
I asked this nearly two years ago; doesn't anyone else have this problem?
Is it fixed in newer versions of Vim?
$ vim some_file
and upon hitting enter, immediately type, "io", sometimes Vim ends up in
command mode as if nothing was typed, then sometimes it ends up in an
insert on the same line, and the character o has been entered, as expected.
Most of the time, though, one of the other four possibilities happens,
based on which of the two characters is dropped, or both: it's in command
mode, insert mode on the same line, or insert mdoe with a new line having
been o)pened.
If you type something "immediately" after invoking Vim from the sh
command-line, then there is a race condition between the human typing at
the keyboard and the system loading and starting Vim. I would expect
that the io input would or would not be received, or even might be
received only partly, by Vim, depending on who wins the race.

A possible reason for this race condition to exist (rather than Vim
using the system's keyboard-typeahead buffer as found as startup) is
that when running in an xterm, Vim tries to determine the xterm version
by querying the xterm (sending a control command to the "display") and
waiting for a corresponding response stream of control characters on its
"keyboard" input line. If this is the case you should not experience any
loss in the non-X Linux consoles (accessed by Ctrl-Alt-F1 to
Ctrl-Alt-F6; come back to X by Ctrl-Alt-F7 or Ctrl-Alt-F8 depending on
"I don't know what").

IMHO the "healthy" way to proceed would be for the human to wait on Vim
displaying either the ":intro" splash screen or the contents of the
first editfile before proceeding to type anything at the keyboard. This
way (i.e. with a wait loop inserted into the human's firmware) it can be
guaranteed that there is no race condition, and that Vim is always in a
known state when the first keyboard byte is sent to the console or X
interface.


Best regards,
Tony.
--
I think I'll KILL myself by leaping out of this 14th STORY WINDOW while
reading ERICA JONG'S poetry!!
--8<---------------cut here---------------end--------------->8---
Nuno Silva
2023-12-22 07:53:35 UTC
Permalink
[...]
Post by Julieta Shem
Post by Kaz Kylheku
Post by Julieta Shem
Post by Kaz Kylheku
TCSAFLUSH is evil; if your user is a fast typist who can enter keyboard
input before your program has initialized, they will find that their
buffered keystrokes were lost. You won't easily see it in a small
program whose executable and libraries are cache hot, and which doesn't
read a lot of startup files and such.
(Vim does this annoying flush thing. I posted about it to the mailing
list many years ago and was rudely dismissed.)
Lol. Do you have a message-id or something? I'd love to have a look.
According to my own mail archives, I first posted about it to the
"Typeahead character loss when running Vim.".
prepended, on 2014-02-18, almost two years later.
I don't see any of the replies in my archives, though I subscribed
to the list at the time. I might have looked at the archive for
the reply.
You got two replies in 2014. Have a look at
https://groups.google.com/g/vim_use/c/XX6SoxHAzhI
[...]

The post I'm replying to already has the contents of the 2014 post and
of its two replies, I'm just mentioning the Message-IDs in case these
are useful (although, as this is a mailing list, it's possible Google
groups actually does give access to the Message-IDs?):

The 2012 post:
- <64a254c0-ac6e-4492-9931-***@mq9g2000pbb.googlegroups.com>

The 2014 posts:
- <***@mail.kylheku.com>
- <***@phoenix>
- <***@gmail.com>

These are all on gmane too. If you have a browser that can load news:
URLs:
- news://news.gmane.io/64a254c0-ac6e-4492-9931-***@mq9g2000pbb.googlegroups.com
- news://news.gmane.io/***@mail.kylheku.com
- news://news.gmane.io/***@phoenix
- news://news.gmane.io/***@gmail.com

(I don't see line break issues in the 2014 posts, is it possible these
are being added by the google groups interface?)
--
Nuno Silva
Janis Papanagnou
2023-12-22 11:48:55 UTC
Permalink
[ from the archives; character loss on Vim startup ]
Post by Kaz Kylheku
When I run $ vim file.txt from the shell, and start typing a command
like /quicksort,
some of the characters end up missing. It might end up /quickt.
For this specific but not uncommon usecase - to start and immediately
locate the desired place - you could work around the issue by starting

$ vim +/quicksort file.txt

instead of

$ vim file.txt
/quicksort

Doesn't address the Vim issue directly but may result in a better user
experience of the interface for fast typers.

(It certainly will be difficult to change habits, though, to relocate
the first command to the vim call. Myself I also first start the tool.)

Janis
Julieta Shem
2023-12-22 20:27:42 UTC
Permalink
Post by Janis Papanagnou
[ from the archives; character loss on Vim startup ]
Post by Kaz Kylheku
When I run $ vim file.txt from the shell, and start typing a command
like /quicksort,
some of the characters end up missing. It might end up /quickt.
For this specific but not uncommon usecase - to start and immediately
locate the desired place - you could work around the issue by starting
$ vim +/quicksort file.txt
instead of
$ vim file.txt
/quicksort
Doesn't address the Vim issue directly but may result in a better user
experience of the interface for fast typers.
(It certainly will be difficult to change habits, though, to relocate
the first command to the vim call. Myself I also first start the tool.)
I would bet Kaz Kylheku was just reporting the flaw, which apparently
the maintainers of the software couldn't care enough. Is that why we
all run EMACS? :-)
Kaz Kylheku
2023-12-20 18:18:42 UTC
Permalink
Post by Janis Papanagnou
So thanks again. I don't know *why* it works (or rather, why it didn't
work before), but now [without the fcntl()] it does. :-)
Are you using C stdio after setting STDIN_FILENO to nonblocking?

If so, you will be constantly getting errors on stdin whenever the
underlying descriptor returns EWOULDBLOCK. Every time that happens
you have to call clearerr(stdin).

The only reason you'd want the descriptor nonblocking is that your
program has some busy calculation to do during which it needs to
periodically check for TTY input.

As soon as your program has nothing to do but wait for input,
you'd need to use poll() or select(), or else switch the descriptor
to blocking again. So as not to sit in a tight loop calling the
nonblocking read.
Post by Janis Papanagnou
[*] It would certainly have been good if I'd understand why the fcntl()
on stdin was in the way, and why it affected output. - But okay, that's
just my honest desire to try to understand what I'm doing. ;-)
Probably because your program blows through the non-blocking reads,
which affects its behavior. Output that would not have been produced
until a keystroke is read is now produced immediately, or whatever.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
Janis Papanagnou
2023-12-21 09:48:36 UTC
Permalink
Post by Kaz Kylheku
Post by Janis Papanagnou
So thanks again. I don't know *why* it works (or rather, why it didn't
work before), but now [without the fcntl()] it does. :-)
Are you using C stdio after setting STDIN_FILENO to nonblocking?
I had been using C stdout after setting STDIN_FILENO to nonblocking
but not C stdin; input came solely from the keystroke "commands".
Post by Kaz Kylheku
If so, you will be constantly getting errors on stdin whenever the
underlying descriptor returns EWOULDBLOCK. Every time that happens
you have to call clearerr(stdin).
The only reason you'd want the descriptor nonblocking is that your
program has some busy calculation to do during which it needs to
periodically check for TTY input.
This is the case. It constantly does something and the processing is
only affected by the ad hoc keystrokes.
Post by Kaz Kylheku
As soon as your program has nothing to do but wait for input,
you'd need to use poll() or select(), or else switch the descriptor
to blocking again. So as not to sit in a tight loop calling the
nonblocking read.
When the processing steps are done for all simulated entities the
program calls usleep() for a calculated well defined period and gets
effectively suspended.
Post by Kaz Kylheku
Post by Janis Papanagnou
[*] It would certainly have been good if I'd understand why the fcntl()
on stdin was in the way, and why it affected output. - But okay, that's
just my honest desire to try to understand what I'm doing. ;-)
Probably because your program blows through the non-blocking reads,
which affects its behavior. Output that would not have been produced
until a keystroke is read is now produced immediately, or whatever.
The effect was the opposite; that output that had previously been
created did not show up any more. (After removing the non-blocking
_input_ control setting the _output_ showed up again, as desired.)

Janis
M***@dastardlyhq.com
2023-12-19 10:06:45 UTC
Permalink
On Tue, 19 Dec 2023 10:46:02 +0100
Post by Janis Papanagnou
I'm trying to get some pressed keys control a piece of software.
And I'm using ANSI escapes to control terminal screen output.
Both works nicely in separate tests, but assembled the former
seems to affect the latter. So I am looking for hints/caveats on
what I am possibly doing wrong.
The "poll pressed key" function is basically derived from Steven's
APUE book (tty_cbreak). The main functional parts are the following
settings...
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
and
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Also are you sure fd is stdin? Perhaps use STDIN_FILENO.
Kaz Kylheku
2023-12-19 20:47:06 UTC
Permalink
Post by M***@dastardlyhq.com
Also are you sure fd is stdin? Perhaps use STDIN_FILENO.
I suspect the purpose of STDIN_FILENO is readability, not abstraction
(as in the possibility of it being redefined).

Newcomers to Unix codebases may be confused if they see a hard
coded literal zero.

There is no way in hell stdin can be changed to anything other
than zero.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
Janis Papanagnou
2023-12-19 16:47:02 UTC
Permalink
Post by Janis Papanagnou
I'm trying to get some pressed keys control a piece of software.
And I'm using ANSI escapes to control terminal screen output.
Both works nicely in separate tests, but assembled the former
seems to affect the latter. So I am looking for hints/caveats on
what I am possibly doing wrong.
The "poll pressed key" function is basically derived from Steven's
APUE book (tty_cbreak). The main functional parts are the following
settings...
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
and
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Given that there are bazillions of flags to set or clear; is
there anything I should add or omit? (Or something else I should
have an eye on?)
Just to clarify the issue I seem to have after the first replies
and emphasize what works...

Keystrokes can be obtained in a test program using the depicted
method, and ANSI escapes for screen control generally work as well.
But in combination the same screen drawing procedures don't work
any more; that's why I suspected some interference of these termios
flags with the ANSI controls, where the latter are just output by
the C functions printf() or fputs().

Janis
Scott Lurndal
2023-12-19 17:04:05 UTC
Permalink
Post by Janis Papanagnou
I'm trying to get some pressed keys control a piece of software.
And I'm using ANSI escapes to control terminal screen output.
Both works nicely in separate tests, but assembled the former
seems to affect the latter. So I am looking for hints/caveats on
what I am possibly doing wrong.
The "poll pressed key" function is basically derived from Steven's
APUE book (tty_cbreak). The main functional parts are the following
settings...
fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
and
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
Given that there are bazillions of flags to set or clear; is
there anything I should add or omit? (Or something else I should
have an eye on?)
Use poll(2) instead of read(2). IIRC, O_NONBLOCK doesn't necessarily
apply to PTYs.

diag = ::tcgetattr(u_ttyfd, &u_term_attr_orig);
if (diag == -1) {
u_logger->log("%s: Unable to get attributes for '%s': %s\n",
u_appname, devname, strerror(errno));
goto leave;
}

u_term_attr_cur = u_term_attr_orig;
::cfmakeraw(&u_term_attr_cur);
u_term_attr_cur.c_lflag = 0;
u_term_attr_cur.c_oflag |= OPOST|ONLCR;

diag = ::tcsetattr(u_ttyfd, TCSANOW, &u_term_attr_cur);
if (diag == -1) {
u_logger->log("%s: Unable to set attributes for '%s': %s\n",
u_appname, devname, strerror(errno));
goto leave;
}
u_term_attr_set = true;
Nicolas George
2023-12-19 18:29:56 UTC
Permalink
Post by Scott Lurndal
Use poll(2) instead of read(2). IIRC, O_NONBLOCK doesn't necessarily
apply to PTYs.
O_NONBLOCK applies to ptys, and if it did not then poll() would not either.
Post by Scott Lurndal
diag = ::tcgetattr
What is this ugly horror?
Scott Lurndal
2023-12-19 18:42:48 UTC
Permalink
Post by Nicolas George
Post by Scott Lurndal
Use poll(2) instead of read(2). IIRC, O_NONBLOCK doesn't necessarily
apply to PTYs.
O_NONBLOCK applies to ptys, and if it did not then poll() would not either.
Actually, poll should work just fine without O_NONBLOCK,
but that said, you are correct that O_NONBLOCK applies to ptys.

I use:

u_ttyfd = ::open(devname, O_RDWR|O_NOCTTY|O_NONBLOCK, 0);
Post by Nicolas George
Post by Scott Lurndal
diag = ::tcgetattr
What is this ugly horror?
Why do you care? This isn't comp.lang.c.
Kaz Kylheku
2023-12-19 20:55:19 UTC
Permalink
Post by Nicolas George
Post by Scott Lurndal
Use poll(2) instead of read(2). IIRC, O_NONBLOCK doesn't necessarily
apply to PTYs.
O_NONBLOCK applies to ptys, and if it did not then poll() would not either.
I don't believe that true. There is no requirement that non-blocking
objects be used with poll, at least if you are only reading.

When a descriptor polls positive for input, then a subsequent input
operation will not block. There is no requirement to use O_NONBLOCK, or
for the device to support it.

You need O_NONBLOCK if you're polling for writing, because a positive
write poll only tells you that there is some buffer space available,
without indicating how much. If the device is blocking and the
subsequent write is too large, it can still block.

However, I think, logically, one byte must be guaranteed: if a blocking
device polls positive for writing, we can be confident we can do a one
byte write without blocking, provided the device isn't shared with
any other process that could stuff it behind our back.
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
Andreas Kempe
2023-12-19 21:59:45 UTC
Permalink
Post by Kaz Kylheku
Post by Nicolas George
Post by Scott Lurndal
Use poll(2) instead of read(2). IIRC, O_NONBLOCK doesn't necessarily
apply to PTYs.
O_NONBLOCK applies to ptys, and if it did not then poll() would not either.
I don't believe that true. There is no requirement that non-blocking
objects be used with poll, at least if you are only reading.
When a descriptor polls positive for input, then a subsequent input
operation will not block. There is no requirement to use O_NONBLOCK, or
for the device to support it.
You're right that O_NONBLOCK is not needed for polling since polling
consists of the polling process asking the driver if it has data
available and if the device doesn't, the process is put to sleep until
data is available or the timeout is hit.

On the other hand, I can't really imagine a scenario where it would
make sense for a device to be pollable, but not support non-blocking
reads. If you can tell whether data is ready, you should be able to
support non-blocking reads.
Post by Kaz Kylheku
You need O_NONBLOCK if you're polling for writing, because a positive
write poll only tells you that there is some buffer space available,
without indicating how much. If the device is blocking and the
subsequent write is too large, it can still block.
However, I think, logically, one byte must be guaranteed: if a blocking
device polls positive for writing, we can be confident we can do a one
byte write without blocking, provided the device isn't shared with
any other process that could stuff it behind our back.
Depends on the implementation in the driver being polled really. A
sane implementation would of course not signal that it is ready for
writing unless it has some space available. In a multi-processor
system, or for a device conceivably being shared with some other
hardware, you could have races for the buffer space. You would then
likely get an EAGAIN error when you try to write, indicating that no
space is available. This is of course also true for non-blocking reads
with parallel access.
Scott Lurndal
2023-12-19 22:12:27 UTC
Permalink
Post by Andreas Kempe
Post by Kaz Kylheku
Post by Nicolas George
Post by Scott Lurndal
Use poll(2) instead of read(2). IIRC, O_NONBLOCK doesn't necessarily
apply to PTYs.
O_NONBLOCK applies to ptys, and if it did not then poll() would not either.
I don't believe that true. There is no requirement that non-blocking
objects be used with poll, at least if you are only reading.
When a descriptor polls positive for input, then a subsequent input
operation will not block. There is no requirement to use O_NONBLOCK, or
for the device to support it.
You're right that O_NONBLOCK is not needed for polling since polling
consists of the polling process asking the driver if it has data
available and if the device doesn't, the process is put to sleep until
data is available or the timeout is hit.
On the other hand, I can't really imagine a scenario where it would
make sense for a device to be pollable, but not support non-blocking
reads. If you can tell whether data is ready, you should be able to
support non-blocking reads.
int diag = ioctl(tty_fd, FIONREAD, &num_to_read);
Nicolas George
2023-12-19 22:28:43 UTC
Permalink
[The reply has not reached my server, I reply to this one:]
Post by Scott Lurndal
Post by Kaz Kylheku
Post by Nicolas George
O_NONBLOCK applies to ptys, and if it did not then poll() would not either.
I don't believe that true. There is no requirement that non-blocking
objects be used with poll, at least if you are only reading.
When a descriptor polls positive for input, then a subsequent input
operation will not block. There is no requirement to use O_NONBLOCK, or
for the device to support it.
Read better: I have not said that you had to use non-blocking IO along with
poll, I have said that if O_NONBLOCK does not work, the poll() does not work
either.

If you think I am wrong, then just provide a counter-example.

But you will not be able to, because non-blocking IO and pollable IO are
internally the same thing.

(Strange OS-specific devices might not respect that statement; that is
irrelevant for the discussion.)
Post by Scott Lurndal
int diag = ioctl(tty_fd, FIONREAD, &num_to_read);
Are you trying to say something?
Janis Papanagnou
2023-12-19 19:17:56 UTC
Permalink
Use poll(2) instead of read(2). [...]
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

It isn't explicitly mentioned in the man-page, but I assume a
timeout value of 0 will actually do the non-blocking behavior
with poll(2).
diag = ::tcgetattr(u_ttyfd, &u_term_attr_orig);
if (diag == -1) {
u_logger->log("%s: Unable to get attributes for '%s': %s\n",
u_appname, devname, strerror(errno));
goto leave;
}
u_term_attr_cur = u_term_attr_orig;
::cfmakeraw(&u_term_attr_cur);
u_term_attr_cur.c_lflag = 0;
u_term_attr_cur.c_oflag |= OPOST|ONLCR;
diag = ::tcsetattr(u_ttyfd, TCSANOW, &u_term_attr_cur);
if (diag == -1) {
u_logger->log("%s: Unable to set attributes for '%s': %s\n",
u_appname, devname, strerror(errno));
goto leave;
}
u_term_attr_set = true;
At the moment I don't understand what this code does; I suppose
I'll have to look up all those flags first.

Thanks.

Janis
Scott Lurndal
2023-12-19 20:30:48 UTC
Permalink
Post by Janis Papanagnou
Use poll(2) instead of read(2). [...]
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
It isn't explicitly mentioned in the man-page, but I assume a
timeout value of 0 will actually do the non-blocking behavior
with poll(2).
diag = ::tcgetattr(u_ttyfd, &u_term_attr_orig);
if (diag == -1) {
u_logger->log("%s: Unable to get attributes for '%s': %s\n",
u_appname, devname, strerror(errno));
goto leave;
}
u_term_attr_cur = u_term_attr_orig;
::cfmakeraw(&u_term_attr_cur);
u_term_attr_cur.c_lflag = 0;
u_term_attr_cur.c_oflag |= OPOST|ONLCR;
diag = ::tcsetattr(u_ttyfd, TCSANOW, &u_term_attr_cur);
if (diag == -1) {
u_logger->log("%s: Unable to set attributes for '%s': %s\n",
u_appname, devname, strerror(errno));
goto leave;
}
u_term_attr_set = true;
At the moment I don't understand what this code does; I suppose
I'll have to look up all those flags first.
The key is cfmakeraw which sets up the attributes
for raw mode.

Raw mode

From the man page

cfmakeraw() sets the terminal to something like the "raw" mode
of the old Version 7 terminal driver: input is available character
by character, echoing is disabled, and all special processing of
terminal input and output characters is disabled. The terminal
attributes are set as follows:

termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;

Comes from BSD originally.

(You can ignore the leading :: on system calls, that's a C++-ism).
Janis Papanagnou
2023-12-19 20:53:47 UTC
Permalink
Post by Scott Lurndal
[...]
So far I haven't stumbled across cfmakeraw(), thanks.

And thanks for the explanations.
Post by Scott Lurndal
(You can ignore the leading :: on system calls, that's a C++-ism).
I'm familiar with C++ ::-)

Janis ;-)
Rainer Weikusat
2023-12-27 17:56:24 UTC
Permalink
Post by Janis Papanagnou
Use poll(2) instead of read(2). [...]
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
It isn't explicitly mentioned in the man-page, but I assume a
timeout value of 0 will actually do the non-blocking behavior
with poll(2).
SUS says

If the value of timeout is 0, poll() shall return immediately.

https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html

The Linux man page has this as

Specifying a timeout of zero causes poll() to return immediately, even
if no file descriptors are ready.

Kaz Kylheku
2023-12-19 21:06:54 UTC
Permalink
Post by Janis Papanagnou
struct termios buf;
buf.c_lflag &= ~(ECHO | ICANON);
buf.c_cc[VMIN] = 1;
buf.c_cc[VTIME] = 0;
tcsetattr(fd, TCSAFLUSH, &buf);
This is fine. It's not fully raw. For instance, the Ctrl-C (or
whatever is your break character) to SIGINT mapping is still active.

Maybe you want that; not every character-at-a-time-input program
wants to disable Ctrl-C; just those that handle it as a character
and implement it themselves. See IGNBRK, BRKINT.

E.g. in an interpreted language's listener, we want Ctrl-C to
just cancel the current line of input and start a new one.
A nice way to do that is to just treat it as a command character,
rather than mess around with signals.

I think that in non-canonical mode, CR-NL translation is
may still be in effect (ICRNL, INLCR). If you want that raw,
those have t obe turned off.

You can experiment with all these flags in a shell script,
since they are available via stty.

You can do a precise one byte read from the tty using:

dd bs=1 count=1

But when code is running, we do want the interrupt, so we flip out
of that raw mode when the command line is not being edited.

Be sure to save the tty state using

tty_state=$(stty -g)

that returns a string which can be passed as an argument to stty
to restore the settings (like in an EXIT or INT trap of the
shell script). I think, POSIX says that this stty -g string does
not require quoting:

stty $tty_state
--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @***@mstdn.ca
NOTE: If you use Google Groups, I don't see you, unless you're whitelisted.
Loading...