Closed dankamongmen closed 3 years ago
i think assuming that the terminal communicates back to us via escape codes is reasonable. so if we're not in an escape code, punt that over to the general input queue. this ought take care of e.g. input piped in.
note that in #1506 we stopped attempting terminal interrogation when TIOCGSIZE
doesn't return pixel information, which has proven a pretty good heuristic thus far.
can't we just open /dev/tty
as a new file descriptor and talk to that directly? wouldn't that at least fix the piped-input case?
can't we just open /dev/tty as a new file descriptor and talk to that directly?
I'm not familiar enough with notcurses to say if this is a problem or not, but unless you are sure stdin/stdout is a tty you need to open, and use /dev/tty
. Of course, it will still be intermingled with user input, if stdin is the tty.
i think assuming that the terminal communicates back to us via escape codes is reasonable.
FWIW, I'm not aware of any query replies that aren't escapes. In fact, I'm not aware of anything, besides user input, that isn't escapes. Foot never sends anything that isn't an escape, except for user input.
A common trick to avoid waiting forever for replies to queries that aren't supported on all terminals is to send another query right after the first one. The last one should be one that you are sure (as sure as one can be) is supported on all terminals (Send Device Attributes, \E[c
, is a common choice). This way, you'll get at least something returned. You'll have to parse the response to determine if you got one, or two replies.
A common trick to avoid waiting forever for replies to queries that aren't supported on all terminals is to send another query right after the first one. The last one should be one that you are sure (as sure as one can be) is supported on all terminals (Send Device Attributes,
\E[c
, is a common choice). This way, you'll get at least something returned. You'll have to parse the response to determine if you got one, or two replies.
if there is a truly general such code, this is a very elegant way to handle the problem; i love it! <3 <3 <3 <3 <3 <3
if there is a truly general such code
Let me put it this way; I'm not aware of any terminals that do not support \E[c
.
so i have one last concern before implementing this sweet technique: how does this interact with someone redirecting stdin? the particular case i'm worrying about is when a large source is sent to stdin (someone holding down on a key might presumably have the same result, read on).
even if i don't have an internal thread reading off input, and instead am just checking input when either (a) i've just written something expecting a result, or (b) the user requests it, let's say there's a 2GB file being sent to stdin. i would need to buffer all of that in the (a) case, since the user hasn't asked for it yet, and it's going to come before my expected answer from the terminal.
of course, if stdin is redirected to a file, it presumably is not getting input from the tty like normal. so perhaps i can read responses exclusively on the actual terminal fd (i.e. the one to which i wrote the query), and it ought never see redirected stdin. regular stdin (i.e. from a tty) would still need to be buffered in this case, and indeed, it might be broken across both stdin and the termfd...we'll need experiment with that.
and this is of course predicated on successful implementation of #1692, which i intend to handle very soon.
You should be reading terminal query responses directly from the TTY, i.e. not stdin. Simply because you'll never get any replies if you read from a re-directed stdin.
But, you're right, if stdin is not re-directed, there's a chance you'll be reading user input before the reply arrives.
Fun fact: the terminal itself has more or less the same problem when pasting text from the clipboard. And yes, in this case, foot buffers everything else until the paste is complete, then it drains the buffered writes.
So, it's very unlikely you'll get 2GB user input on the TTY. A re-directed stdin, yes, but you're not reading TTY replies from that. However, you can get fairly large amounts of data if the user pastes a large amount of text.
If stdin is not re-directed, then it and your term FD refers to the same underlying TTY FD. And, the stdin FILE *
stream may be buffered by the OS already. Not sure how you've set things up? So you absolutely need to be careful how you read from them.
it would be good to go ahead and implement this ASAP, since it'll allow us to run on both mainbranch and graphicsbranch alacritty.
You should be reading terminal query responses directly from the TTY, i.e. not stdin. Simply because you'll never get any replies if you read from a re-directed stdin.
I'm never reading from stdin; i'm worried about when it's not redirected, user input being interpreted as terminal responses. but so long as terminal responses are always escaped, and arrive atomically (not safe to assume with old terminals, but hopefully safe now--i'm not getting into any ESCDELAY
business), they ought be distinguishable. i think we're good to go here!
but so long as terminal responses are always escaped, and arrive atomically (not safe to assume with old terminals, but hopefully safe now--i'm not getting into any ESCDELAY business), they ought be distinguishable
I don't think you can assume that. You can probably assume that a single ESC is just that, but not that you'll have the entire response atomically. I think it's theoretically possible to get a single ESC in one read, even though it's part of a longer response.
but so long as terminal responses are always escaped, and arrive atomically (not safe to assume with old terminals, but hopefully safe now--i'm not getting into any ESCDELAY business), they ought be distinguishable
I don't think you can assume that. You can probably assume that a single ESC is just that, but not that you'll have the entire response atomically. I think it's theoretically possible to get a single ESC in one read, even though it's part of a longer response.
ugh, that leads to the whole ESCDELAY
situation that NCURSES has -- how long do you wait, following receipt of an \e, before handing it all off to the user as regular input?
so far as i can tell, options include
there are some guarantees of atomicity of sufficiently small write(2)
s to a pipe, but i don't think they apply to this case, and i'd be hesitant to rely on them anyway. obviously there's no hope of atomicity if the reply is written across multiple write()
s, but i'd hope that doesn't happen. what are your thoughts?
i'm thinking that, rather than send DA and then conditionally send 2x(XTSMGRAPHICS+DA), we ought just send 2xXTSMGRAPHICS+DA at the beginning. it cuts two round-trip times and several bytes, we need the same parsing logic either way (pump that lemma!), and it makes our behavior more deterministic. furthermore, i'd say we probably ought toss this out immediately upon starting, so the reply is ideally available by the time we've initialized, minimizing latency to quiescence.
i'm thinking that, rather than send DA and then conditionally send 2x(XTSMGRAPHICS+DA), we ought just send 2xXTSMGRAPHICS+DA at the beginning. it cuts two round-trip times and several bytes, we need the same parsing logic either way (pump that lemma!), and it makes our behavior more deterministic. furthermore, i'd say we probably ought toss this out immediately upon starting, so the reply is ideally available by the time we've initialized, minimizing latency to quiescence.
shit, if we assume we're always going to get a reply from DA, we can eliminate the entire notcurses_check_pixel_support()
fecaoma, which would make me truly happy. ok, let's go for it!
so to do this we'd just make sixel support conditional on successful parse of the second XTSMGRAPHICS, or better yet both of them. in that case, there's no need to trigger it off any particular DA response. yes, i love this!
I think that any write, regardless how small, may be split up if the pipe is already almost full? That's where I think the theoretical problem lies.
obviously there's no hope of atomicity if the reply is written across multiple write()s, but i'd hope that doesn't happen
I do think that may happen. Not sure how common. In foot, query replies are usually send in a single write()
, but not always. For example, OSC-52 (clipboard data) is split up. DA
and XTGRAPHICS
responses are sent in single writes. But more importantly, alt-prefixed user input is split up, so that the ESC is sent first, and the character next.
I don't mind updating foot, but I think that if one terminal send split up responses, then others are too.
I think that any write, regardless how small, may be split up if the pipe is already almost full? That's where I think the theoretical problem lies.
absolutely, but if i throw this off immediately (and i only need it at startup, though that's not true for cursor location queries), it "ought" be among my first inputs. i agree there are fundamental races here, but there seem to be at any selection of timeout (i'm suggesting a choice of 0).
absolutely, but if i throw this off immediately (and i only need it at startup, though that's not true for cursor location queries), it "ought" be among my first inputs
Agreed, I don't think this will be a problem.
ugh, that leads to the whole ESCDELAY situation that NCURSES has
FWIW, foot has a feature to enable a custom escape for Escape, for this exact reason. Not suggesting you use that, because a) it's limited to foot, and b) we're likely to implement Kitty's new keyboard protocol which should make our custom escape obsolete.
so i just did some testing. i have an open terminal:
[schwarzgerat](0) $ ls -l /proc/$$/fd
total 0
lrwx------ 1 dank dank 64 2021-06-09 22:10 0 -> /dev/pts/10
lrwx------ 1 dank dank 64 2021-06-09 22:10 1 -> /dev/pts/10
lrwx------ 1 dank dank 64 2021-06-09 22:10 2 -> /dev/pts/10
lrwx------ 1 dank dank 64 2021-06-09 22:10 255 -> /dev/pts/10
[schwarzgerat](0) $
in another terminal, i start flooding the pty with zeroes:
[schwarzgerat](0) $ cat /dev/zero > /dev/pts/10
i see that my victim terminal is now pegging a core, eating all dem zeroes.
i then run notcurses-info
, without any redirection on stdin:
[schwarzgerat](127) $ ./notcurses-info
[schwarzgerat](0) $
and we get interesting effects for sure
i'm now sending XTGETTCAP
along with my other queries in the dankamongmen/shittin-for-real
branch. let's see what all manner of replies we get!
alright, we're about to move forward.
one complexity i haven't noted above is that escape sequences aren't necessarily delimited, which complicates our automaton.
here's my plan: we already encode a trie of escape sequences in ncinputlayer
, and all this code is common to direct and rendered mode. we'll develop this trie out. augment the trie nodes to have either an nckey
or a callback, presumably accepting a termdesc
(and a ttyfd, if that's not in termdesc
already).
if stdin
is connected to our tty (need lock down the details of how we'll tell), we have to accept stdin input into a unified state machine. otherwise, we needn't, and should have two distinct ones (and two distinct tries).
early in initialization, fire off a single write(2)
including:
TDA to check for foot
SDA to get various information, which might need TDA to decode properly (version for foot, decTerminalID for xterm)
XTGETTCAP
with Pt=TN
to check for kitty (Xterm doesn't seem to be replying, despite docs?)
XTSMGRAPHICS to set color register count
XTSMGRAPHICS for color register count
XTSMGRAPHICS for sixel dimensions
DA to ensure a reply
https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/8 is worth reading
now the user cannot yet be reading input, because initialization hasn't completed (though maybe we ought let initialization conclude, and only block the user if they attempt to use a feature relying on this stuff? later.). so we can drive ncinputlayer
from our thread of execution, and the only thing we need deal with is potentially shoving data traffic into a queue (if stdin
is connected to our terminal).
if we get a \EP!|464f4f54\E\\
, that's foot (DCS prefix)
if we get a \E[>1;XXYYZZ;0c
, that's foot's version/xterm's decTerminalID (CSI prefix)
if we get a \EP1+rTN=hexstring\\
that's XTGETTCAP announcing the terminal name
if we get a \E[?2;
, that's sixel size
if we get a \E[?1;
, that's color register count
otherwise watch for DA responses (varied)
and attach callbacks to each of these save DA. when we get the DA response, we return control to initialization, which completes, returning control to the user.
the secondary case is when we're already running and need an async query. the primary example would be cursor position lookup. here, the user may be performing input processing themselves.
if stdin
isn't connected to the tty, it's no problem, and we can blockingly read on tttyfd.
otherwise, we'd need have a secondary queue that gets dumped into by either one of the two paths, but only one at a time, and we'll need a completion-based dequeue
for it. no big troubles.
https://terminalguide.namepad.de/seq/csi_sc__r/ looks like we get VTE with DA3/TDA, good good
yep from xfce4-terminal we get :!|7E565445
for TCA
work is proceeding here, and i expect to have this in today. we're now sending one Grand Unified Query terminated with the send primary device attributes query, and ending initial discovery upon receipt of a primary DA response. now i need to fill in the state machine in pump_control_read()
, and ensure non-control-sequence input is shunted over to the user queue.
Cool!
Just an FYI: foot recently added support for XTVERSION
(CSI > q
). This is in addition to the already existing DA responses. Maybe in some distant future all terminals implement it and you can cut down on the number of queries you send :)
argh, are Device Attributes and XTSMGRAPHICS replies really indistinguishable without lookahead/lookback? fuck my life
Yeah... might want/have to implement a simple VT parser... Both replies are on the CSI
form, using ?
as intermediate character, but with different final characters (c
vs. S
).
(Have you seen https://vt100.net/emu/dec_ansi_parser ?)
Yeah... might want/have to implement a simple VT parser... Both replies are on the
CSI
form, using?
as intermediate character, but with different final characters (c
vs.S
).
that's what i'm doing, just this complicates things. i'm just about done in any case. https://github.com/dankamongmen/notcurses/compare/dankamongmen/shittin-for-real
(Have you seen https://vt100.net/emu/dec_ansi_parser ?)
// FIXME ought implement the full Williams automaton
static int
pump_control_read(init_state* inits, unsigned char c){
alright, verified that we're lexing up to the point of recovering color register and sixel geometry information from the replies in XTerm, foot, and sixel-enabled Alacritty. this is good progress. let's lex the actual replies out and ensure we're once again capable of driving sixel (it's currently broken in this branch, having disabled the existing XTSMGRAPHICS logic). i think we can merge at that point, as this will want as much and as varied testing as is possible.
we'll then want to do the second part, which is terminal identification based on these responses. that will require moving the parsing prior to the heuristics, since we'll want to use the derived terminal name rather than TERM
as input to same.
note that this comes after terminfo lookup and control sequence extraction, meaning a pretty correct TERM
is still necessary. this will only control our heuristics-derived features. i'm not sure whether we can ever get rid of terminfo. a stepping stone would be using the solved terminal name as input to setupterm()
(requiring that derived names match up with terminfo names). otherwise we'd essentially have to carry the terminfo database inside us, which is stupid.
b) we're likely to implement Kitty's new keyboard protocol which should make our custom escape obsolete.
ahhh, glad to hear this!
alright, we're no longer seeing unexpected transitions in any of the aforementioned terminals. time to draw out sixel information. we then need figure out why notcurses-tester
is broken.
state: 14 char: 2 50 32
state: 17 char: S 83 53
state: 17 char: 27 1b
state: 1 char: [ 91 5b
state: 2 char: ? 63 3f
state: 12 char: 2 50 32
state: 18 char: ; 59 3b
state: 19 char: 0 48 30
state: 20 char: ; 59 3b
state: 21 char: 4 52 34
state: 21 char: 0 48 30
state: 21 char: 9 57 39
state: 21 char: 6 54 36
state: 21 char: ; 59 3b
4096 max sixel width!
state: 22 char: 4 52 34
state: 22 char: 0 48 30
state: 22 char: 9 57 39
state: 22 char: 6 54 36
state: 22 char: S 83 53
4096 max sixel height!
state: 0 char: 27 1b
state: 1 char: [ 91 5b
state: 2 char: ? 63 3f
state: 12 char: 1 49 31
state: 13 char: ; 59 3b
state: 14 char: 0 48 30
state: 15 char: ; 59 3b
state: 23 char: 1 49 31
state: 23 char: 0 48 30
state: 23 char: 2 50 32
state: 23 char: 4 52 34
state: 23 char: S 83 53
1024 color registers!
state: 0 char: 27 1b
state: 1 char: [ 91 5b
state: 2 char: ? 63 3f
state: 12 char: 6 54 36
state: 16 char: c 99 63
yay!
alacritty works with pixels once more, fuck yeah bitches
notcurses 2.3.4 by nick black et al on Alacritty
70 rows (20px) 80 cols (10px) (87.50KiB) 48B crend 256 colors+RGB
compiled with gcc-10.2.1 20210110, 16B little-endian cells
terminfo from ncurses 6.2.20201114
avformat 58.79.100 avutil 56.74.100 swscale 5.10.100
Colors: 256 rgb: y ccc: y setaf: y setab: y
sgr: y sgr0: y op: y fgop: y bgop: y
rows: 70 cols: 80 rpx: 20 cpx: 10 (1400x800)
max sixel size: 4096x4096 colorregs: 1024
UTF8: y quad: y sex: n braille: y images: y videos: y
Halves { ▀▄█} Quads { ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█} Light ⎧┌┐─⎫ Heavy ⎧┏┓━⎫ ⎧█ ⎫ 🯰🯱
Sextants ⎧ 🬀🬁🬂🬃🬄🬅🬆🬇🬈🬊🬋🬌🬍🬎🬏🬐🬑🬒🬓▌🬔🬕🬖🬗🬘🬙🬚🬛🬜🬝⎫ ⎩└┘│⎭ ⎩┗┛┃⎭ ⎪🮋▏⎪ 🯲🯳
⎩🬟🬠🬡🬢🬣🬤🬥🬦🬧▐🬨🬩🬪🬫🬬🬭🬮🬯🬰🬱🬲🬳🬴🬵🬶🬷🬸🬹🬺🬻█⎭ Round ⎧╭╮─⎫ Frame ⎧╔╗═⎫ ⎪🮊▎⎪ 🯴🯵
Braille ⎡⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎤ ⎩╰╯│⎭ ⎩╚╝║⎭ ⎪🮉▍⎪ 🯶🯷
⎢⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎥ ⎨▐▌⎬ 🯸🯹
⎢⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎥ ⎪🮈▋⎪
⎣⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎦ ⎪🮇▊⎪
Vert ⅛s ⎛ ▁▂▃▄▅▆▇█⎞ ▔🭶🭷🭸🭹🭺🭻▁ ⎪▕▉⎪
⎝█🮆🮅🮄▀🮃🮂▔ ⎠ ⎩ █⎭
background isn't interpreted as transparent
cup: y vpa: y hpa: y
[schwarzgerat](0) $
XTerm, checking in
notcurses 2.3.4 by nick black et al on XTerm
61 rows (23px) 80 cols (11px) (76.25KiB) 48B crend 256 colors+RGB
compiled with gcc-10.2.1 20210110, 16B little-endian cells
terminfo from ncurses 6.2.20201114
avformat 58.79.100 avutil 56.74.100 swscale 5.10.100
Colors: 256 rgb: y ccc: y setaf: y setab: y
sgr: y sgr0: y op: y fgop: y bgop: y
rows: 61 cols: 80 rpx: 23 cpx: 11 (1403x880)
max sixel size: 1411x880 colorregs: 256
UTF8: y quad: n sex: n braille: y images: y videos: y
Halves { ▀▄█} Quads { ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█} Light ⎧┌┐─⎫ Heavy ⎧┏┓━⎫ ⎧█ ⎫ 🯰🯱
Sextants ⎧ 🬀🬁🬂🬃🬄🬅🬆🬇🬈🬊🬋🬌🬍🬎🬏🬐🬑🬒🬓▌🬔🬕🬖🬗🬘🬙🬚🬛🬜🬝⎫ ⎩└┘│⎭ ⎩┗┛┃⎭ ⎪🮋▏⎪ 🯲🯳
⎩🬟🬠🬡🬢🬣🬤🬥🬦🬧▐🬨🬩🬪🬫🬬🬭🬮🬯🬰🬱🬲🬳🬴🬵🬶🬷🬸🬹🬺🬻█⎭ Round ⎧╭╮─⎫ Frame ⎧╔╗═⎫ ⎪🮊▎⎪ 🯴🯵
Braille ⎡⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎤ ⎩╰╯│⎭ ⎩╚╝║⎭ ⎪🮉▍⎪ 🯶🯷
⎢⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎥ ⎨▐▌⎬ 🯸🯹
⎢⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎥ ⎪🮈▋⎪
⎣⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿⎦ ⎪🮇▊⎪
Vert ⅛s ⎛ ▁▂▃▄▅▆▇█⎞ ▔🭶🭷🭸🭹🭺🭻▁ ⎪▕▉⎪
⎝█🮆🮅🮄▀🮃🮂▔ ⎠ ⎩ █⎭
background isn't interpreted as transparent
cup: y vpa: y hpa: y
[schwarzgerat](0) $
foot's here for the party
w00000000000000000000000000t!
foot's here for the party
hey @dnkl , fix your horizontal 1/8ths, i would not hit it
here's approximately what i'd like to see (though kitty's left curly braces don't seem to be matching its curly extender, who knows whether it's a font thing or not)
man, that is one big, grotesque state machine!
confirmed WezTerm is here to party, good work @wez
hey @dnkl , fix your horizontal 1/8ths, i would not hit it
You're on the stable 1.7.2 release? I believe this has already been fixed, if you run latest master:
(note: foot doesn't render any brace extenders itself, but with this particular font they still look good :))
hey @dnkl , fix your horizontal 1/8ths, i would not hit it
You're on the stable 1.7.2 release? I believe this has already been fixed, if you run latest master:
what kind of christmas-in-london-ass-font is this lol =D
alright, that was a slog, but i think we're ready to merge the initial phase of work -- unified single query, from which we opportunistically extract sixel parameters. this allowed us to kill off alacritty_sixel_hack
, nice.
@dnkl if i'm in a control sequence that we know to be split up in such a fashion, i can explicitly wait. what i don't want to do is wait following arbitrary control-sequence-looking-things that I don't recognize before passing them up to the user. Theoretically, I ought not be getting control sequences I don't recognize, since they're only coming in response to actions I take (save bracketed paste maybe?). i'm handling alt-prefixed characters fine right now, though i can't claim to be doing so perfectly under rough conditions....
alright, anyway, this issue has gone on for a long time. we know how we're proceeding now. next thing is to do opportunistic certain terminal detection using these results -- i.e., let them override TERM
for purposes of heuristics. not yet sure whether we want to do so for terminfo; we'll see.
what kind of christmas-in-london-ass-font is this lol =D
Fantasque Sans Mono, my goto font when I need a vector font :)
Currently, if we interrogate a terminal, and don't get a response, the program will simply hang. Hence the requirement that the user explicitly call some function (e.g.
notcurses_check_pixel_support()
) before we begin interrogation.I hate this so fucking much. I have no idea whether user input can break up the terminal response, but it wouldn't surprise me at all (and if it doesn't break the response, we'll at best lose said input). Hate hate hate hate hate.
I've been unwilling to put in a timeout, because on a loaded system, a slow reply is totally normal, and what timeout would we use, and even worse would be pushing that timeout question to the user, because they know nothing. I stand by this decision, because I am a dinosaur and a curmudgeon and everyone's too busy these days. Let the computer cogitate, if that's what it needs to do.
I do think, however, that we need at least print a diagnostic after a certain time goes by without a response, so the user isn't just left with no idea what's up. I think this can be dumped to stderr; it'll be jarring and disrupt any drawn screen, but hey, you've been sitting around for five (or whatver) stimulus-free seconds already, and in all likelihood you'll be sitting there until time ends or the power fails.
So yeah, make a concession to reality, and after say 5 seconds without a response from the terminal, print a diagnostic to stderr about "N seconds without a response from the terminal....". Maybe include the
TERM
value in this output for the inevitable bug reports.Better yet would be a method to break user input up from terminal responses, but I doubt that this is possible =[. Maybe we ought take unrecognized input and queue it up as user input, but then we're just forwards-incompatible with new, wacky returns from tomorrow's terminals.
Fuck the world, save yourself.