虽然 Stack Overflow 上的问题很老,但它肯定不过时,因此我写了一个完整的示例来说明如何做这个。
基本方法是:
- 启用 stdout 上 ANSI 转义序列的处理。
- 禁用 stdin 上的 ECHO 和行模式。
- 发送 ANSI 序列来查询光标位置到 stdout。
- 在 stdin 上读取回复。
- 恢复 stdin 和 stdout 的设置。
对于第一步,在 Linux 下,ANSI 转义序列的处理应该是默认启用的,但在 Windows 下它们没有被启用,至少目前是这样的,这就是为什么下面的示例使用 SetConsoleMode 来启用它们。关于 kernel32.GetStdHandle() - 调用,Windows 标准句柄的 stdin 是 -10,而 stdout 则是 -11,我们只是获取了这些文件描述符。这些都是 Windows 特有的函数。
至于 Linux,我们可以使用 termios 来禁用/启用 ECHO 和行模式。值得注意的是,termios 在 Windows 下不可用。
对于第二步,stdin 上的任何输入都会被缓冲,并且只会逐行发送,但我们希望尽快读取 stdin 上的所有输入。我们还希望禁用 ECHO,这样第三步的回复就不会被打印到控制台上。
为了保险起见,下面的示例将在出现问题时给出 (-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
以下链接可能会有所帮助,如果您想深入研究所有这些,并扩展这里的功能:Linux的termios手册页、Windows SetConsoleMode、Windows GetConsoleMode、维基百科的ANSI转义序列条目