简体   繁体   English

如何从 Python 在 ZAEA23489CE3AA9B63406EBB28E0 终端中获取当前文本 cursor position

[英]How to get the current text cursor position from Python in a Windows Terminal?

Getting the current mouse pointer position in Python is trivial, through the use of the Windows API ctypes libraries.通过使用 Windows ctypes库,在 Python 中获取当前鼠标指针 position 很简单。 However, it seem that taking the step from the mouse pointer's screen position position (x,y) , to get the current text cursor position of the current terminal window, seem a huge difficulty. However, it seem that taking the step from the mouse pointer's screen position position (x,y) , to get the current text cursor position of the current terminal window, seem a huge difficulty.

In addition it makes it worse that the programmer community keep confusing mouse pointer position with text cursor position.此外,程序员社区不断将鼠标指针position 与文本cursor position 混淆,这使情况变得更糟。 Historically there never was a mouse "cursor" so when people are saying "cursor", they should mean text cursor, and not the other way around.历史上从来没有鼠标“光标” ,所以当人们说“光标”时,他们应该指的是文本 cursor,而不是相反。 Because of this error, Stackoverflow is full of questions and answers, relating to "cursor", but seemingly none relates to getting the terminal shell's current character position .由于这个错误,Stackoverflow 充满了与“光标”相关的问题和答案,但似乎与获取终端外壳的当前字符 position 无关 [The cursed cursor!] [被诅咒的光标!]

To get the relative mouse pointer position :要获取相对鼠标指针 position

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='')

I want to have a function that give me the last position in terms of character row and column .我想要一个 function 在字符方面给我最后一个 position 。 Perhaps something like this:也许是这样的:

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

How do I get the text cursor position in ( row,column ) inside my terminal?如何在终端内的(行,列)中获取text cursor position?
(And without making any assumptions and having to write my own window manager?) (并且不做任何假设并且必须编写我自己的 window 管理器?)

Ultimately I hope to use this to find the last cursor position in any terminal window, (and possibly used by any program?)最终我希望用它在任何终端window中找到最后一个cursor position,(并且可能被任何程序使用?)


Possibly related (but not useful) SO Questions:可能相关(但无用)的 SO 问题:


UPDATE (2022-01-17)更新(2022-01-17)

Looking through the MS documentation, I am now convinced it should be possible to get this from the (older, non-VT-based) API call, GetConsoleScreenBufferInfo which is given like this.查看 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;

So in light of this, I was thinking the following would work.因此,鉴于此,我认为以下方法会起作用。

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

The problem was to locate the various Structure definitions.问题是找到各种结构定义。 After having experimented significantly, I've got the following working solution.经过大量实验后,我得到了以下工作解决方案。

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

The OP's solution fails in ( my ) Windows environment: OP 的解决方案在(我的)Windows 环境中失败:

pip install readline

returns error: this module is not meant to work on Windows (truncated).返回error: this module is not meant to work on Windows (截断)。

I have found another solution for pure Windows environment (without the GNU readline interface . Desired structure definitions borrowed at programtalk.com , see lines 1..91:我找到了另一种纯 Windows 环境的解决方案(没有GNU readline 接口。在programtalk.com借用的所需结构定义,参见第 1..91 行:

# These functions are part of the python-colorama module
# They have been adjusted slightly for dosage
#
# Copyright: (C) 2010 Jonathan Hartley <tartley@tartley.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name(s) of the copyright holders nor the
#    names of its contributors may be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


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

Output : .\SO\70732748.py Output.\SO\70732748.py

12345 (5,526)

From line 113, there is an attempt to resolve discrepancy between buffer and screen size (unsuccessful, absolute screen line number is incorrectly computed, at least if terminal is scroll-forwarded ).从第 113 行开始,尝试解决缓冲区和屏幕大小之间的差异(不成功,绝对屏幕行号计算不正确,至少在终端滚动转发的情况下)。

Example : .\SO\70732748.py x示例.\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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM