Как я могу получить позицию курсора в терминале 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 - но я думаю, что это может быть сделано методом проб и ошибок.

Хотя вопрос здесь о переполнении стека устарел, он определенно не устарел, и поэтому я написал полный пример того, как это сделать.

Базовый подход:

  1. Включите обработку управляющих последовательностей ANSI на stdout .
  2. Отключите ECHO и линейный режим на стандартном вводе .
  3. Отправьте последовательность ANSI для запроса позиции курсора на стандартном выводе .
  4. Прочтите ответ на стандартном вводе .
  5. Восстановите настройки для 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-последовательностей

Другие вопросы по тегам