Telnet cursor keys
Julie Stamp (8365) 474 posts |
I like !NetTask as a way to log in to RISC OS from Linux, but I can’t use the cursor keys to get command-line history. I had a go at trying to make them work, but I got stuck because the cursor key is sent to us as CHR$&1B+"[A" or similar, and we don’t know when we get the escape whether it’s going to be part of this sequence or a real escape key Does anyone have a clever idea about how to make the escape |
Rick Murray (539) 13907 posts |
If I recall, there wasn’t really an Escape key on telnet. It was pretty much always used to introduce a command code. For an escape key on its own, various solutions cropped up:
None of this is likely to work on a real terminal (like a VT102) given that double ESC just restarts the command sequence and it’ll dumbly wait for following data… but coming the other way the server side could be a little less brain-dead. Probably the best way, if you can, is to peek the input buffer. Is there data following or is it just an ESC code? If no data, wait a mo. If still nothing that looks like a command sequence, assume it’s a lone escape. [this, by the way, is why the Unix world uses keys like ^C to abort, the ESC key didn’t do stuff like that, it was around the home computer revolution that the ESC key became associated with “stop”] |
Julie Stamp (8365) 474 posts |
Thanks, it sounds like a Linux terminal that can send a double escape when I press the Escape key would be ideal if anyone knows of one? |
Steve Pampling (1551) 8198 posts |
Well the putty ssh/telnet client has source for more than just windows builds and Unix as well as windows-on-arm source is available |
Charles Ferguson (8243) 429 posts |
In general, the ‘Escape’ key is detected by either the escape code (\e, 0×1b, 27) being sent and then a delay with nothing being sent is detected. Some terminals will send the escape code twice (ie 27, 27) to indicate such a state. This is in contrast to the codes for cursor keys which will depend on the terminal emulation mode used by the client sending the codes. The cursor keys will start with an escape code, followed by the VT codes you are interested in. These will differ depending on whether you are in ANSI mode, VT52 mode, and whether the application mode has been selected or not. https://vt100.net/docs/vt220-rm/chapter3.html#S3.2 gives a reasonable table of the basics here – that document isn’t canonical, but it is a good reference (as are the other terminal documents on that site) for how most systems will function. The cursor keys (and function keys) may also be preceded by a modifier code. This is by default ‘1’ (if it’s not specified). Other modifiers are a bitmask of the modifiers which are currently in force:
So if you were to receive “CSI (ESC ‘[’) 1 ; A”, this would be an unmodified cursor up. “CSI 2 ; A” would be shift-Up and “CSI 5 ; A” would be ctrl-Up. (1+1 => shift and 1+5 => ctrl, respectively). Wikipedia has a nice summary here: https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_input_sequences But also note the addition of a count number at the start in the documentation here: https://github.com/0×5c/VT100-Examples/blob/master/vt_seq.md#input-sequences and here: https://github.com/mintty/mintty/wiki/Keycodes#cursor-keys And this also present in the Windows terminal VT input sequences: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#input-sequences For reference, the translations used by RISC OS Pyromaniac for console input in POSIX systems is limited, but covers many of these cases. A large table of the base escape codes is used: escape_codes = { # Standard sequences '[A': '\x8F', # Up '[B': '\x8E', # Down '[C': '\x8D', # Right '[D': '\x8C', # Left '[F': '\x8B', # End (Copy in RISC OS terms) '[H': '\x1E', # Home '[Z': '\x09', # Shift-Tab # Application sequences 'OP': '\x81', # F1 'OQ': '\x82', # F2 'OR': '\x83', # F3 'OS': '\x84', # F4 'OH': '\x1E', # Home 'OF': '\x8B', # End # VT sequences '[1~': '\x1E', # Home (don't know what this should be) #'[2~': '\x89', # Insert (don't know what this should be) '[3~': '\x7F', # Delete '[4~': '\x8B', # End '[5~': '\x9F', # Page Up '[6~': '\x9E', # Page Down '[7~': '\x1E', # Home '[8~': '\x87', # End '[11~': '\x81', # F1 '[12~': '\x82', # F2 '[13~': '\x83', # F3 '[14~': '\x84', # F4 '[15~': '\x85', # F5 # Note 16 isn't mapped '[17~': '\x86', # F6 '[18~': '\x87', # F7 '[19~': '\x88', # F8 '[20~': '\x89', # F9 '[21~': '\xCA', # F10 # Note 22 isn't mapped '[23~': '\xCB', # F11 '[24~': '\xCC', # F12 '[25~': '\x80', # Print (don't know what this should be) # Shifted keys '[1;2P': '\x91', # Shift-F1 '[1;2Q': '\x92', # Shift-F2 '[1;2R': '\x93', # Shift-F3 '[1;2S': '\x94', # Shift-F4 '[15;2~': '\x95', # Shift-F5 '[17;2~': '\x96', # Shift-F6 '[18;2~': '\x97', # Shift-F7 '[19;2~': '\x98', # Shift-F8 '[20;2~': '\x99', # Shift-F9 '[21;2~': '\xDA', # Shift-F10 '[23;2~': '\xDB', # Shift-F11 '[24;2~': '\xDC', # Shift-F12 '[1;2A': '\x9F', # Shift-Up '[1;2B': '\x9E', # Shift-Down '[1;2C': '\x9D', # Shift-Right '[1;2D': '\x9C', # Shift-Left '[1;2F': '\x9B', # Shift-End (Copy in RISC OS terms) '[1;2H': '\x1E', # Shift-Home # Ctrled keys '[1;5H': '\x1E', # Ctrl-Home '[1;5F': '\xAB', # Ctrl-End (Copy in RISC OS terms) } Together with a parser which extracts the modifiers and applies them for the ‘~’ terminated codes. There is also a timeout for the escape alone, which is by default set to 0.2 seconds. The input parser takes the processed escape code up to the terminating character (A-Z, a-z or ~) and decodes out a RISC OS code: def parse_escape(self, seq): if len(seq) == 0 or (len(seq) == 1 and seq[0] == '\x1b'): # Literal escape key! # FIXME: Should we set the escape flags here too? return '\x1b' if seq[0] == '[': code = seq[-1] if (code >= 'A' and code <= 'Z') or (code >= 'a' and code <= 'z'): # letter codes might have numbers preceding them for modifiers. # a modifier of 1 means 'no modifier', so we can strip it if len(seq) == 3 and seq[1] == '1': # Reduce it to the un-modifier version so our dictionary is simpler seq = ['[', code] elif code == '~': # The [<num>;<modifier>~ sequence can also have a modifier of 1, # so we simplify this as well. if len(seq) > 3 and seq[-3] == ';' and seq[-2] == '1': seq = seq[:-3] seq.append('~') # FIXME: The modifier might be +1 for Shift, +2 for Alt, +4 for Ctrl seq = ''.join(seq) value = escape_codes.get(seq, None) if self.debug_inputescapes: if value: print("Input escape: Sequence %r => %r" % (seq, value)) else: print("Input escape: Sequence %r not recognised" % (seq,)) return value Hopefully, some of that is helpful in identifying what codes can be used… but this is a system that has been accreted since the 70s and in many cases, clients have just used what worked for them with the applications that were in use, so yes, it’s complicated. And I’m almost certainly doing it wrong too :-) |
Colin (478) 2433 posts |
Can’t you just parse the input to nettask – it’s only a short basic program. At present nettask sees escape as an escape key pressed even if it is part of a sequence. If you parse the input so that ‘esc’ followed by [ is part of an escape sequence then you can filter out keypresses and convert them to what you want. There are obviously other escape codes but keypresses are likely to start esc[ |
Colin (478) 2433 posts |
Unfortunately that doesn’t work. I don’t see a way to send a key code for the cursor keys. The only solution I can see is for NetTask to implement the line editor
|
Charles Ferguson (8243) 429 posts |
I’m pretty sure my implementation in !TelnetD worked ok, albeit quite a limited selection of the cases were handled: 4450 WHEN Telnet_ESC:REM For all the escape codes 4460 IF len%>1 THEN 4470 cmd%=rbuf%(sock%)?1 4480 IF cmd%=ASC("[") THEN 4490 IF len%>2 THEN 4500 cmd%=rbuf%(sock%)?2 4510 PROCsyslog(log_telnet,FNduser(sock%)+" Escape code "+STR$cmd%+" "+CHR$cmd%) 4520 CASE cmd% OF 4530 WHEN ASC("A"):REM Up 4540 ?rbuf%(sock%)=0:rbuf%(sock%)?1=&8F 4550 PROCsend(sock%,rbuf%(sock%),2):PROCrelease(rbuf%(sock%)) 4560 WHEN ASC("B"):REM Down 4570 ?rbuf%(sock%)=0:rbuf%(sock%)?1=&8E 4580 PROCsend(sock%,rbuf%(sock%),2):PROCrelease(rbuf%(sock%)) 4590 WHEN ASC("C"):REM Right 4600 ?rbuf%(sock%)=0:rbuf%(sock%)?1=&8D 4610 PROCsend(sock%,rbuf%(sock%),2):PROCrelease(rbuf%(sock%)) 4620 REM Won't work properly 4630 WHEN ASC("D"):REM Left 4640 ?rbuf%(sock%)=0:rbuf%(sock%)?1=&8C 4650 PROCsend(sock%,rbuf%(sock%),2):PROCrelease(rbuf%(sock%)) 4660 WHEN ASC("5"):REM shift-up (if followed by ~) 4670 IF len%>3 THEN 4680 cmd%=rbuf%(sock%)?3 4690 IF cmd%=ASC("~") THEN 4700 REM FIXME: This doesn't work - taskwindow reading actual keyboard? 4710 ?rbuf%(sock%)=&9c:rbuf%(sock%)?1=&9c 4720 PROCsend(sock%,rbuf%(sock%),2):PROCrelease(rbuf%(sock%)) 4730 ELSE 4740 PROCsend(sock%,rbuf%(sock%),4):PROCrelease(rbuf%(sock%)) 4750 ENDIF 4760 ENDIF 4770 OTHERWISE 4780 PROCsend(sock%,rbuf%(sock%),3):PROCrelease(rbuf%(sock%)) 4790 ENDCASE 4800 ENDIF 4810 ELSE 4820 PROCsend(sock%,rbuf%(sock%),2):PROCrelease(rbuf%(sock%)) 4830 ENDIF 4840 ENDIF I’m pretty sure the FIXME comment is wrong; it’s more likely due to the different modes that the function key input can be in as to which codes are sent. And sending &9c twice just looks wrong – it should probably be sending 0 and then &9f for the shift-up, from the example Python code, but then it was written in ’96, so… I might let myself off for not quite understanding the input system. On the other hand, maybe the above didn’t work, and I just don’t remember. |
Colin (478) 2433 posts |
Interesting that 0 acts as an escape character. The patch below sort of works. Up and down go through the history buffer but don’t clear from the cursor to end of line. Left and right doesn’t work. I’d still be inclined to write the line editor in the basic program sending chars and escape sequences back to the client and whole lines to the taskwindow when cr is pressed.
|
Charles Ferguson (8243) 429 posts |
I’d assume that’s to do with the translation the other way. LineEditor probably uses the VDU codes for ‘clear to end of line’, so you’d need to translate that back the other way to make it do the right thing in VT-land. See the VDU chapter for the particular combinations but you’d need to convert from VDU 23,8 to an equivalent VT code. The ‘clear to right of cursor’ is ‘CSI [ 0 K’. However it’s possible that LineEditor doesn’t do this, and instead does a step to the end of the line character at a time with VDU 9, and then delete with VDU 127. If that’s the case, then you’d also need to be translating VDU 9 into ‘CSI 1 C’ as a code 9 in VT-land is a tab, which moves to the next tab position, which won’t be what you want. Or if could be reading the screen position and moving around with that, using VDU 31, x,y. In which case it’s not going to work under a TaskWindow – which probably means that it won’t be doing that.
The problem with doing that is that you don’t know that that’s what the client running under the TaskWindow wants. If it’s waiting for you to ‘Press a key to continue’ then you won’t be able to insert just one key. Maybe that’s fine, but it’s a little more limiting. |