如何在 Windows 终端中从 Python 获取当前文本光标位置?

问题描述 投票:0回答:3

通过使用 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
python python-3.x windows winapi dll
3个回答
1
投票

问题是找到各种结构定义。经过大量实验后,我得到了以下工作解决方案。

#!/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)

1
投票

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)

0
投票
Blessed 库中的

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 毫秒,这对于我的用例来说是完全可以接受的。

© www.soinside.com 2019 - 2024. All rights reserved.