通过使用 Windows API
ctypes
库,在 Python 中获取当前鼠标指针位置非常简单。然而,似乎从鼠标指针的屏幕位置(x,y)开始,获取当前终端窗口的当前文本光标位置,似乎是一个巨大的困难。
此外,程序员社区不断混淆鼠标 pointer 位置与文本 cursor 位置,这让情况变得更糟。 历史上从来没有鼠标“光标”所以当人们说“光标”时,他们应该指的是文本光标,而不是相反。由于这个错误,Stackoverflow 上充满了与“光标”相关的问题和答案,但似乎没有一个与获取终端 shell 的当前字符位置有关。 [被诅咒的光标!]
获取相对鼠标指针位置:
from ctypes import windll, wintypes, byref
def get_cursor_pos():
cursor = wintypes.POINT()
windll.user32.GetCursorPos(byref(cursor))
return (cursor.x, cursor.y)
while(1): print('{}\t\t\r'.format(get_cursor_pos()), end='')
我想要一个函数,根据 character row 给出最后一个位置 和列。也许是这样的:
def cpos():
xy = here_be_magic()
return xy
# Clear screen and start from top:
print('\x1b[H', end='');
print('12345', end='', flush=True); xy=cpos(); print('( {},{})'.format(xy[0],xy[1]),end='', flush=True)
# 12345 (1,5) # written on top of blank screen
如何获取终端内 (
row,column) 中的
text
光标位置?最终我希望用它来查找任何终端窗口中的最后一个光标位置(并且可能被任何程序使用?)
可能相关(但没用)所以问题:
更新(2022-01-17)
查看 MS 文档,我现在确信应该可以从(较旧的、非基于 VT 的)API 调用中获取此信息,
GetConsoleScreenBufferInfo
,如下所示。
BOOL WINAPI GetConsoleScreenBufferInfo(
_In_ HANDLE hConsoleOutput, # A handle to the console screen buffer. The handle must have the GENERIC_READ access right.
_Out_ PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo # A pointer to a CONSOLE_SCREEN_BUFFER_INFO structure that receives the console screen buffer information.
);
typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
COORD dwSize; # contains the size of the console screen buffer, in character columns and rows.
COORD dwCursorPosition; # contains the column and row coordinates of the cursor in the console screen buffer.
WORD wAttributes; # Character attributes (divided into two classes: color and DBCS)
SMALL_RECT srWindow; # A SMALL_RECT structure that contains the console screen buffer coordinates of the upper-left and lower-right corners of the display window.
COORD dwMaximumWindowSize; # A COORD structure that contains the maximum size of the console window, in character columns and rows, given the current screen buffer size and font and the screen size.
} CONSOLE_SCREEN_BUFFER_INFO; #
# Defines the coordinates of a character cell in a console screen buffer.
# The origin of the coordinate system (0,0) is at the top, left cell of the buffer.
typedef struct _COORD {
SHORT X; # The horizontal coordinate or column value. The units depend on the function call.
SHORT Y; # The vertical coordinate or row value. The units depend on the function call.
} COORD, *PCOORD;
typedef struct _SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;
} SMALL_RECT;
因此,鉴于此,我认为以下方法会起作用。
cls='\x1b[H'
from ctypes import windll, wintypes, byref
def cpos():
cursor = wintypes._COORD(ctypes.c_short)
windll.kernel32.GetConsoleScreenBufferInfo(byref(cursor))
return (cursor.X, cursor.Y)
cpos()
# TypeError: '_ctypes.PyCSimpleType' object cannot be interpreted as an integer
问题是找到各种结构定义。经过大量实验后,我得到了以下工作解决方案。
#!/usr/bin/env python -u
# -*- coding: UTF-8 -*-
#------------------------------------------------------------------------------
from ctypes import windll, wintypes, Structure, c_short, c_ushort, byref, c_ulong
from readline import console
#------------------------------------------------
# Win32 API
#------------------------------------------------
SHORT = c_short
WORD = c_ushort
DWORD = c_ulong
STD_OUTPUT_HANDLE = DWORD(-11) # $CONOUT
# These are already defined, so no need to redefine.
COORD = wintypes._COORD
SMALL_RECT = wintypes.SMALL_RECT
CONSOLE_SCREEN_BUFFER_INFO = console.CONSOLE_SCREEN_BUFFER_INFO
#------------------------------------------------
# Main
#------------------------------------------------
wk32 = windll.kernel32
hSo = wk32.GetStdHandle(STD_OUTPUT_HANDLE)
GetCSBI = wk32.GetConsoleScreenBufferInfo
def cpos():
csbi = CONSOLE_SCREEN_BUFFER_INFO()
GetCSBI(hSo, byref(csbi))
xy = csbi.dwCursorPosition
return '({},{})'.format(xy.X,xy.Y)
cls='\x1b[H'
print('\n'*61)
print(cls+'12345', end='', flush=True); print(' {}'.format(cpos()), flush=True)
# 12345 (5,503)
OP 的解决方案在(my)Windows 环境中失败:
Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import readline Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'readline' >>>
和
pip install readline
返回 error: this module is not meant to work on Windows
(截断)。
我找到了另一种纯 Windows 环境的解决方案(没有 GNU readline 接口。所需的结构定义借用于 programtalk.com,请参阅第 1..91 行:
# from winbase.h
STDOUT = -11
STDERR = -12
from ctypes import (windll, byref, Structure, c_char, c_short, c_uint32,
c_ushort, ArgumentError, WinError)
handles = {
STDOUT: windll.kernel32.GetStdHandle(STDOUT),
STDERR: windll.kernel32.GetStdHandle(STDERR),
}
SHORT = c_short
WORD = c_ushort
DWORD = c_uint32
TCHAR = c_char
class COORD(Structure):
"""struct in wincon.h"""
_fields_ = [
('X', SHORT),
('Y', SHORT),
]
class SMALL_RECT(Structure):
"""struct in wincon.h."""
_fields_ = [
("Left", SHORT),
("Top", SHORT),
("Right", SHORT),
("Bottom", SHORT),
]
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
"""struct in wincon.h."""
_fields_ = [
("dwSize", COORD),
("dwCursorPosition", COORD),
("wAttributes", WORD),
("srWindow", SMALL_RECT),
("dwMaximumWindowSize", COORD),
]
def __str__(self):
"""Get string representation of console screen buffer info."""
return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
self.dwSize.Y, self.dwSize.X
, self.dwCursorPosition.Y, self.dwCursorPosition.X
, self.wAttributes
, self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
, self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
)
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
"""Get console screen buffer info object."""
handle = handles[stream_id]
csbi = CONSOLE_SCREEN_BUFFER_INFO()
success = windll.kernel32.GetConsoleScreenBufferInfo(
handle, byref(csbi))
if not success:
raise WinError()
return csbi
### end of https://programtalk.com/vs4/python/14134/dosage/dosagelib/colorama.py/
clrcur = '\x1b[H' # move cursor to the top left corner
clrscr = '\x1b[2J' # clear entire screen (? moving cursor ?)
# '\x1b[H\x1b[2J'
from ctypes import windll, wintypes, byref
def get_cursor_pos():
cursor = wintypes.POINT()
aux = windll.user32.GetCursorPos(byref(cursor))
return (cursor.x, cursor.y)
mouse_pos = get_cursor_pos()
# print('mouse at {}'.format(mouse_pos))
def cpos():
csbi = GetConsoleScreenBufferInfo()
return '({},{})'.format(csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y)
print('12345', end='', flush=True)
print(' {}'.format(cpos()), flush=True)
# an attempt to resolve discrepancy between buffer and screen size
# in the following code snippet:
import sys
if len(sys.argv) > 1 and len(sys.argv[1]) > 0:
csbi = GetConsoleScreenBufferInfo()
keybd_pos = (csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y)
print('\nkbd buffer at {}'.format(keybd_pos))
import os
screensize = os.get_terminal_size()
keybd_poss = ( csbi.dwCursorPosition.X,
min( csbi.dwSize.Y,
csbi.dwCursorPosition.Y,
csbi.dwMaximumWindowSize.Y,
screensize.lines))
# screen line number is incorrectly computed if termial is scroll-forwarded
print('kbd screen at {} (disputable? derived from the following data:)'
.format(keybd_poss))
print( 'csbi.dwSize ', (csbi.dwSize.X, csbi.dwSize.Y))
print( 'terminal_size ', (screensize.columns, screensize.lines))
print( 'csbi.dwMaxSize', (csbi.dwMaximumWindowSize.X, csbi.dwMaximumWindowSize.Y))
print( 'csbi.dwCurPos ', (csbi.dwCursorPosition.X, csbi.dwCursorPosition.Y))
输出:
.\SO\70732748.py
12345 (5,526)
从第 113 行开始,尝试解决缓冲区和屏幕大小之间的差异差异(不成功,绝对屏幕行数计算不正确,至少在终端是滚动转发时)。 差异不会出现在Windows Terminal中,总是
buffer height == window height
,所有这些计算都是不必要的......
示例:
.\SO\70732748.py x
12345 (5,529)
kbd buffer at (0, 530)
kbd screen at (0, 36) (disputable? derived from the following data:)
csbi.dwSize (89, 1152)
terminal_size (88, 36)
csbi.dwMaxSize (89, 37)
csbi.dwCurPos (0, 530)
get_location() 使我能够轻松检索 Windows 中的文本光标位置。这是一个例子:
from blessed import Terminal
term = Terminal()
cursor_y_pos, cursor_x_pos = term.get_location()
print(cursor_y_pos, cursor_x_pos)
我的输出是 (29, 0)(第 29 行和第 0 列),但它当然会根据您的情况而有所不同。
get_location() 的文档页面警告说,该函数“由于其延迟,在大多数应用程序中不会被建议”;然而,我发现 get_position() 的执行时间仅为 110-145 毫秒,这对于我的用例来说是完全可以接受的。