I want to get the cursor's position in a terminal window. I know I can echo -e "\033[6n"
and read
the output -s
silently as in this answer, but how can I do this in Python?
I've tried this contextmanager like this:
with Capturing() as output:
sys.stdout.write("\e[6n")
print(output)
but it only captures the \e[6n
('\x1b[6n'
) escape sequence I write, not the ^[[x;yR1
sequence I need.
I've also tried spawning a subprocess
and getting its output, but again, only the escape sequence I write is captured:
output = subprocess.check_output(["echo", "\033[6n"], shell=False)
print(output)
shell=True
makes the output be a list of empty strings.
Avoiding curses
(because this is supposed to be a simple, poor man's cursor pos getter), how can I get the escape sequence returned by printing \e[6n
?
The cursor position is represented by the line number and the character number and signifies where the next character will be displayed. For example, cursor position 1,1 always indicates the upper-leftmost corner position on the terminal. Cursor position 10,30 indicates the 30th character position on the 10th line.
Most terminals understand ANSI escape codes. The relevant codes for this use case: "\033[F" – move cursor to the beginning of the previous line. "\033[A" – move cursor up one line.
While the question here on Stack Overflow is old, it's certainly not outdated, and as such I wrote a complete example of how to do this.
The basic approach is:
For step 1, under Linux handling of ANSI escape sequences on stdout should be enabled by default, but under Windows they aren't, at least at the moment, which is why the example below uses SetConsoleMode to enable those. With regards to the kernel32.GetStdHandle() - calls, the Windows standard handle for stdin is -10 and for stdout it's -11, and we are just getting the file descriptors for those. These are Windows-only functions.
As for Linux, we can use termios to disable/enable ECHO and line mode. Of note is that termios isn't available under Windows.
For step 2, any input on stdin is buffered and only sent forward line-by-line, but we want to read all input on stdin as soon as possible. We also want to disable ECHO, so the reply to step 3 doesn't get printed out to the console.
Just for a good measure, the example below will give a result of (-1, -1) if something went wrong, so your code could e.g. try again.
import sys, re
if(sys.platform == "win32"):
import ctypes
from ctypes import wintypes
else:
import termios
def cursorPos():
if(sys.platform == "win32"):
OldStdinMode = ctypes.wintypes.DWORD()
OldStdoutMode = ctypes.wintypes.DWORD()
kernel32 = ctypes.windll.kernel32
kernel32.GetConsoleMode(kernel32.GetStdHandle(-10), ctypes.byref(OldStdinMode))
kernel32.SetConsoleMode(kernel32.GetStdHandle(-10), 0)
kernel32.GetConsoleMode(kernel32.GetStdHandle(-11), ctypes.byref(OldStdoutMode))
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
else:
OldStdinMode = termios.tcgetattr(sys.stdin)
_ = termios.tcgetattr(sys.stdin)
_[3] = _[3] & ~(termios.ECHO | termios.ICANON)
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, _)
try:
_ = ""
sys.stdout.write("\x1b[6n")
sys.stdout.flush()
while not (_ := _ + sys.stdin.read(1)).endswith('R'):
True
res = re.match(r".*\[(?P<y>\d*);(?P<x>\d*)R", _)
finally:
if(sys.platform == "win32"):
kernel32.SetConsoleMode(kernel32.GetStdHandle(-10), OldStdinMode)
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), OldStdoutMode)
else:
termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, OldStdinMode)
if(res):
return (res.group("x"), res.group("y"))
return (-1, -1)
x, y = cursorPos()
print(f"Cursor x: {x}, y: {y}")
The resulting output should be akin to this:
Cursor x: 1, y: 30
Additional links that may be of use, if one wishes to dig deeper into all this and e.g. expand on the functionality here: Man-page for Linux's termios, Windows SetConsoleMode, Windows GetConsoleMode, Wikipedia-entry for ANSI escape sequences
You can simply read sys.stdin
yourself to get the value.
I found the answer in a question just like yours, but for one trying to do that from a C program:
http://www.linuxquestions.org/questions/programming-9/get-cursor-position-in-c-947833/
So, when I tried something along that from the Python interactive terminal:
>>> import sys
>>> sys.stdout.write("\x1b[6n");a=sys.stdin.read(10)
]^[[46;1R
>>>
>>> a
'\x1b[46;1R'
>>> sys.stdin.isatty()
True
You will have to use other ANSI tricks/position/reprint to avoid the output actually showing up on the terminal, and prevent blocking on stdin read - but I think it can be done with some trial and error.
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