From 88b84e5158bd7bc563d9d15da110955096fe3b5c Mon Sep 17 00:00:00 2001 From: Matthew Clapp Date: Thu, 28 Jan 2021 15:10:06 -0800 Subject: [PATCH] Fix show/hide cursor for older Windows (#610) --- docs/changelog.md | 1 + src/pipx/animate.py | 34 ++++++++++++++++++++++++++++------ src/pipx/util.py | 1 - 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 5942cade37..ace70af17a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,6 @@ dev +- Fix cursor show/hide to work with older versions of Windows. (#610) 0.16.0.0 diff --git a/src/pipx/animate.py b/src/pipx/animate.py index 9de8b9a22a..e01f85a27d 100644 --- a/src/pipx/animate.py +++ b/src/pipx/animate.py @@ -4,13 +4,10 @@ from threading import Event, Thread from typing import Generator, List -from pipx.constants import emoji_support +from pipx.constants import WINDOWS, emoji_support stderr_is_tty = sys.stderr.isatty() - -HIDE_CURSOR = "\033[?25l" -SHOW_CURSOR = "\033[?25h" CLEAR_LINE = "\033[K" EMOJI_ANIMATION_FRAMES = ["⣷", "⣯", "⣟", "⡿", "⢿", "⣻", "⣽", "⣾"] NONEMOJI_ANIMATION_FRAMES = ["", ".", "..", "..."] @@ -19,6 +16,13 @@ MINIMUM_COLS_ALLOW_ANIMATION = 16 +if WINDOWS: + import ctypes + + class _CursorInfo(ctypes.Structure): + _fields_ = [("size", ctypes.c_int), ("visible", ctypes.c_byte)] + + def _env_supports_animation() -> bool: (term_cols, _) = shutil.get_terminal_size(fallback=(0, 0)) return stderr_is_tty and term_cols > MINIMUM_COLS_ALLOW_ANIMATION @@ -96,12 +100,30 @@ def print_animation( break +# for Windows pre-ANSI-terminal-support (before Windows 10 TH2 (v1511)) +# https://stackoverflow.com/a/10455937 +def win_cursor(visible: bool) -> None: + ci = _CursorInfo() + handle = ctypes.windll.kernel32.GetStdHandle(-11) # type: ignore[attr-defined] + ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) # type: ignore[attr-defined] + ci.visible = visible + ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) # type: ignore[attr-defined] + + def hide_cursor() -> None: - sys.stderr.write(f"{HIDE_CURSOR}") + if WINDOWS: + win_cursor(visible=False) + else: + sys.stderr.write("\033[?25l") + sys.stderr.flush() def show_cursor() -> None: - sys.stderr.write(f"{SHOW_CURSOR}") + if WINDOWS: + win_cursor(visible=True) + else: + sys.stderr.write("\033[?25h") + sys.stderr.flush() def clear_line() -> None: diff --git a/src/pipx/util.py b/src/pipx/util.py index f69a5b65af..9add534b93 100644 --- a/src/pipx/util.py +++ b/src/pipx/util.py @@ -169,7 +169,6 @@ def exec_app( # make sure we show cursor again before handing over control show_cursor() - sys.stderr.flush() logger.info("exec_app: " + " ".join([str(c) for c in cmd]))