How to change the location of the pointer in python?
Asked Answered
D

2

12

I want to paint some special words while the program is getting them , actually in real-time . so I've wrote this piece of code which do it quite good but i still have problem with changing the location of the pointer with move keys on keyboard and start typing from where i moved it . can anyone give me a hint how to do it ? here is the CODE :

from colorama import init
from colorama import Fore
import sys
import msvcrt
special_words = ['test' , 'foo' , 'bar', 'Ham']
my_text = ''
init( autoreset = True)
while True:
    c = msvcrt.getch()
    if ord(c) == ord('\r'):  # newline, stop
        break
    elif ord(c) == ord('\b') :
        sys.stdout.write('\b')
        sys.stdout.write(' ')
        my_text = my_text[:-1]
        #CURSOR_UP_ONE = '\x1b[1A'
        #ERASE_LINE = '\x1b[2K'
        #print ERASE_LINE,
    elif ord(c) == 224 :
        set (-1, 1)
    else:
        my_text += c

    sys.stdout.write("\r")  # move to the line beginning
    for j, word in enumerate(my_text.split()):
        if word in special_words:
            sys.stdout.write(Fore.GREEN+ word)
        else:
            sys.stdout.write(Fore.RESET + word)
        if j != len(my_text.split())-1:
            sys.stdout.write(' ')
        else:
            for i in range(0, len(my_text) - my_text.rfind(word) - len(word)):
                sys.stdout.write(' ')
    sys.stdout.flush()
Dynamometer answered 23/12, 2014 at 0:35 Comment(1)
what happens if you run random-walker.py?Knucklehead
I
20

Doing it the easy way

As you already seem to be using the colorama module, the most easy and portable way to position the cursor should be to use the corresponding ANSI controlsequence (see: http://en.m.wikipedia.org/wiki/ANSI_escape_code)

The one you are looking for should be CUP – Cursor Position (CSI n ; m H)positioning the cursor in row n and column m.

The code would look like this then:

def move (y, x):
    print("\033[%d;%dH" % (y, x))

Suffering by doing everything by hand

The long and painful way to make things work even in a windows console, that doesn't know about the above mentioned control sequence would be to use the windows API.

Fortunately the colorama module will do this (hard) work for you, as long as you don't forget a call to colorama.init().

For didactic purposes, I left the code of the most painful approach leaving out the functionality of the colorama module, doing everything by hand.

import ctypes
from ctypes import c_long, c_wchar_p, c_ulong, c_void_p


#==== GLOBAL VARIABLES ======================

gHandle = ctypes.windll.kernel32.GetStdHandle(c_long(-11))


def move (y, x):
   """Move cursor to position indicated by x and y."""
   value = x + (y << 16)
   ctypes.windll.kernel32.SetConsoleCursorPosition(gHandle, c_ulong(value))


def addstr (string):
   """Write string"""
   ctypes.windll.kernel32.WriteConsoleW(gHandle, c_wchar_p(string), c_ulong(len(string)), c_void_p(), None)

As already stated in the comment section this attempt still leaves you with the problem, that your application will only work in the named console, so maybe you will still want to supply a curses version too.

To detect if curses is supported or you will have to use the windows API, you might try something like this.

#==== IMPORTS =================================================================
try:
    import curses
    HAVE_CURSES = True
except:
    HAVE_CURSES = False
    pass
Ingra answered 23/12, 2014 at 1:41 Comment(5)
The code you've provided will only work on a Windows system. It won't run at all on any other OS.Hama
OP uses colorama that is documented (at least) to support positioning ANSI codes. colorama converts ANSI escape sequences into appropriate Win32 API calls; you don't need to make it yourself. Using colorama can also make the code more portable. For simple movements, I'd try blessings instead of curses.Knucklehead
actually what I'm doing is for windows so it doesnt matter if its wont work on the other platforms . so what is your last advice now ? will the above code work ? or there is any better way ? thanksDynamometer
sorry, I didn't update the answer asap. I thought my last advice would already have been clear, agreeing with S.F.Sebastian's advice. Use the functionality of the colorama module, instead of doing anything by hand. As it is exactly the way the colorama package emulates ANSI control sequences, the code now moved to the second section of course works. Nevertheless there is a better way. The one I put at the beginning.Ingra
thanks for the Answer , isnt it easier to use the curses library ? does it do exactly the thing i want ? in real - time ?Dynamometer
L
0

Based on answers from mikyra and https://mcmap.net/q/685968/-how-to-turn-off-blinking-cursor-in-command-window I've managed to compile what I believe to be a full example for Windows, meaning, that you can move the cursor to a certain place, print what you want, and then ensure that the cursor is placed back at the original location.

Issue appears when the console is scrolled. ScreenBufferInfo.window contains lower and upper bound of the currently visible part of console in fields left and right (the structure has been prepared according to what MSDN says in terms of member order). To solve this issue, we need to adjust the y coordinate every time we read it, so that when we have the console filled with text and start to scroll, the cursor position is still correctly read.

I recommend wrapping that functionality in a context manager or decorator.

I hope this supplements the accepted answer.

import ctypes
from ctypes import c_ulong, c_void_p, c_wchar_p


def stdout_handle():
    return ctypes.windll.kernel32.GetStdHandle(-11)


def adjust_for_console_scroll(coord: int):
    kernel32 = ctypes.windll.kernel32
    sbi = _ScreenBufferInfo()
    kernel32.GetConsoleScreenBufferInfo(stdout_handle(), ctypes.byref(sbi))
    whole_buffer_height = sbi.window.right
    visible_buffer_height = sbi.window.right - sbi.window.left

    if int(coord) < int(visible_buffer_height):
        return coord

    if int(whole_buffer_height) > int(coord):
        coord = whole_buffer_height

    return coord


class _Coord(ctypes.Structure):
     _fields_ = [("x", ctypes.c_short), ("y", ctypes.c_short)]


class _SmallRect(ctypes.Structure):
    _fields_ = [
        ("left", ctypes.c_short),
        ("top", ctypes.c_short),
        ("right", ctypes.c_short),
        ("bottom", ctypes.c_short),
    ]


class _ScreenBufferInfo(ctypes.Structure):
    _fields_ = [
        ("size", _Coord),
        ("cursor_pos", _Coord),
        ("attrs", ctypes.c_int),
        ("window", _SmallRect),
        ("max_window_size", _Coord),
    ]


def print_at(text: str, x: int, y: int):
    kernel32 = ctypes.windll.kernel32
    sbi = _ScreenBufferInfo()
    kernel32.GetConsoleScreenBufferInfo(stdout_handle(), ctypes.byref(sbi))
    original_position = sbi.cursor_pos

    # Adjustment is required as the values are read starting from "1", but we
    # need to have zero-indexed values instead.
    x = x - 1
    y = y - 1
    y = adjust_for_console_scroll(y)
    position = x + (y << 16)
    kernel32.SetConsoleCursorPosition(stdout_handle(), position)
    kernel32.WriteConsoleW(
        stdout_handle(), c_wchar_p(text), c_ulong(len(text)), c_void_p(), None
    )
    kernel32.SetConsoleCursorPosition(stdout_handle(), original_position)
Lomax answered 9/9, 2023 at 11:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.