Как я могу получить позицию курсора в терминале ANSI?
Я хочу получить позицию курсора в окне терминала. я знаю что могу echo -e "\033[6n"
а также read
выход -s
тихо, как в этом ответе, но как я могу сделать это в Python?
Я пробовал этот менеджер контекста, как это:
with Capturing() as output:
sys.stdout.write("\e[6n")
print(output)
но это только захватывает \e[6n
('\x1b[6n'
) escape-последовательность, которую я пишу, а не ^[[x;yR1
последовательность мне нужна.
Я также пытался порождать subprocess
и получить его вывод, но опять же, записывается только escape-последовательность, которую я пишу:
output = subprocess.check_output(["echo", "\033[6n"], shell=False)
print(output)
shell=True
делает вывод списком пустых строк.
Как избежать curses
(потому что это должен быть простой, получатель курсора для бедного человека), как я могу получить escape-последовательность, возвращаемую при печати \e[6n
?
2 ответа
Здесь мы идем - вы можете просто прочитать sys.stdout
себя, чтобы получить значение. Я нашел ответ в вопросе, похожем на ваш, но для того, кто пытается это сделать из программы на Си:
http://www.linuxquestions.org/questions/programming-9/get-cursor-position-in-c-947833/
Итак, когда я попробовал что-то подобное с интерактивного терминала Python:
>>> import sys
>>> sys.stdout.write("\x1b[6n");a=sys.stdin.read(10)
]^[[46;1R
>>>
>>> a
'\x1b[46;1R'
>>> sys.stdin.isatty()
True
Вам придется использовать другие трюки / положения / перепечатки ANSI, чтобы избежать вывода вывода на терминал и предотвратить блокировку чтения stdin - но я думаю, что это может быть сделано методом проб и ошибок.
Хотя вопрос здесь о переполнении стека устарел, он определенно не устарел, и поэтому я написал полный пример того, как это сделать.
Базовый подход:
- Включите обработку управляющих последовательностей ANSI на stdout .
- Отключите ECHO и линейный режим на стандартном вводе .
- Отправьте последовательность ANSI для запроса позиции курсора на стандартном выводе .
- Прочтите ответ на стандартном вводе .
- Восстановите настройки для stdin и stdout .
Для шага 1 в Linux обработка управляющих последовательностей ANSI на stdout должна быть включена по умолчанию, но в Windows это не так, по крайней мере, на данный момент, поэтому в приведенном ниже примере для их включения используется SetConsoleMode. Что касается вызовов kernel32.GetStdHandle() , стандартный дескриптор Windows для stdin равен -10, а для stdout - -11, и мы просто получаем для них файловые дескрипторы. Это функции только для Windows.
Что касается Linux, мы можем использовать termios для отключения / включения ECHO и линейного режима. Следует отметить, что termios недоступен в Windows.
На шаге 2 любой ввод на stdin буферизируется и пересылается только построчно, но мы хотим как можно скорее прочитать весь ввод на stdin . Мы также хотим отключить ECHO, чтобы ответ на шаг 3 не выводился на консоль.
На всякий случай приведенный ниже пример даст результат (-1, -1), если что-то пошло не так, поэтому ваш код может, например, повторить попытку.
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}")
Результат должен быть примерно таким:
Cursor x: 1, y: 30
Дополнительные ссылки, которые могут быть полезны, если кто-то хочет углубиться во все это и, например, расширить функциональность здесь: Man-page для Linux's termios, Windows SetConsoleMode, Windows GetConsoleMode, Wikipedia-entry для ANSI escape-последовательностей