I am writing a cross-platform interactive terminal program that receives cursor query responses and mouse events. (The cursor query is actually to detect window size change, since Window Ops are typically disabled.) It works fine in Linux terminals, but in Windows terminals I get interleaved event sequences such as:
\x1b\x1b[46;128R[<32;54;17M
This happens in both PowerShell and cmd.exe. The interleaved sequences always seem to start with \x1b\x1b but I don't find that very reassuring. I can reduce the occurrence of interleaving by turning off mouse events, waiting a delay, querying the cursor, waiting another delay, and turning the mouse events back on. But this makes the program less responsive, and still doesn't eliminate all the occurrences.
How can I deal with these interleaved sequences? Can I get Windows to not interleave? Or is there a standard way I'm supposed to parse the interleaved sequnces? (Where is this documented?)
Reproducing
Run the following script under wsl in a PowerShell or cmd.exe. Then jiggle the mouse for a few seconds.
#!/usr/bin/python2.7
import sys, os, tty, re, time, select
Enable_Mouse_Events = "\x1b[?1003h\x1b[?1006h"
Disable_Mouse_Events = "\x1b[?1006l\x1b[?1003l"
Get_Window_Size = "\x1b[s\x1b[999;999H\x1b[6n\x1b[u"
fd = sys.stdin.fileno()
buf = bytearray()
query_time = Immediately = 0
fail_time = Never = 9e9
def Print(x, end = '\r\n'):
sys.stdout.write(x + end)
sys.stdout.flush()
def Exit(msg):
Print(Disable_Mouse_Events, '')
Print(msg)
time.sleep(0.05)
while select.select([fd,], [], [], 0)[0]:
os.read(fd, 4096)
os._exit(0)
tty.setraw(fd)
Print(Enable_Mouse_Events, '')
while True:
# mouse event?
m = re.match(r'\x1b\[\<(\d*);(\d*);(\d*)([Mm])', buf)
if m:
b, x, y, s = (bytes(s) for s in m.groups())
Print("mouse: b=%s, x=%s, y=%s, s=%s" % (b, x, y, s))
del buf[:len(m.group(0))]
fail_time = Never
continue
# response to cursor query?
m = re.match(r'\x1b\[(\d*);(\d*)R', buf)
if m:
y, x = (bytes(s) for s in m.groups())
Print("window size: x=%s, y=%s" % (x, y))
del buf[:len(m.group(0))]
fail_time = Never
continue
# check for more data
timeout = max(0, min(query_time, fail_time) - time.time())
if not select.select([fd,], [], [], timeout)[0]:
Print(Get_Window_Size, '')
query_time = time.time() + 0.1
if time.time() > fail_time:
Exit('Parsing timed out! buf = %s' % repr(bytes(buf)))
continue
# got more data
buf.extend(os.read(fd, 4096))
if '\x03' in buf:
Exit('^C')
# Set a timeout if not already set and there is more data.
if buf and fail_time is Never:
fail_time = time.time() + 1
I suspect, it's a problem of the "\x1b[6n".
See the difference between linux/windows
Tested with the sequence \x1b[1;1H\x1b[6n\x1b[7;7H\x1b[6n
First it sets the cursor to 1,1 then it request the cursor position,
then set the cursor to 7,7 and request the cursor position again.
On Linux the response is:
\x1b[1;1R\x1b[7;7R
On Windows the response is:
\x1b[7;7R\x1b[1;1R
Linux just appends the answers for queries to the end of the input buffer,
but Windows put the answers (like \x1b[6n and \x1b[0c ) to the front of the input buffer!
This seems to be wrong or at least not very wise.
And it seems that in some situations it might fail if another sequence is to be inserted, like your mouse position.
When a mouse position is to be inserted, it seems possible that the report cursor position answer can interrupt the message, but instead of simple inserting at the front it starts at position 1.
A solution should be possible by checking for two ESC, when the second escape starts a valid sequence of ESC[x;yR then assume you got already the ESC for the next sequence.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With