diff --git a/docs/source/spelling_wordlist.txt b/docs/source/spelling_wordlist.txt index bdacf22a..3ed46647 100644 --- a/docs/source/spelling_wordlist.txt +++ b/docs/source/spelling_wordlist.txt @@ -160,3 +160,4 @@ maxsize reentrant env tty +CPython diff --git a/nvitop/cli.py b/nvitop/cli.py index 6ab7d978..0e8f11cd 100644 --- a/nvitop/cli.py +++ b/nvitop/cli.py @@ -4,13 +4,12 @@ """The interactive NVIDIA-GPU process viewer.""" import argparse -import curses import os import sys import textwrap from nvitop.api import HostProcess, libnvml -from nvitop.tui import TUI, USERNAME, Device, colored, libcurses, set_color, setlocale_utf8 +from nvitop.tui import TUI, USERNAME, Device, colored, curses, libcurses, set_color, setlocale_utf8 from nvitop.version import __version__ diff --git a/nvitop/tui/__init__.py b/nvitop/tui/__init__.py index 173c38d1..0ec63888 100644 --- a/nvitop/tui/__init__.py +++ b/nvitop/tui/__init__.py @@ -8,6 +8,7 @@ USERNAME, Device, colored, + curses, libcurses, set_color, setlocale_utf8, diff --git a/nvitop/tui/library/curses/__init__.py b/nvitop/tui/library/curses/__init__.py new file mode 100644 index 00000000..1244b15e --- /dev/null +++ b/nvitop/tui/library/curses/__init__.py @@ -0,0 +1,8 @@ +# This file is part of nvitop, the interactive NVIDIA-GPU process viewer. +# License: GNU GPL version 3. + +# pylint: disable=missing-module-docstring + +from curses import * # noqa: F403 # pylint: disable=redefined-builtin + +from nvitop.tui.library.curses import ascii # pylint: disable=redefined-builtin diff --git a/nvitop/tui/library/curses/ascii.py b/nvitop/tui/library/curses/ascii.py new file mode 100644 index 00000000..01375840 --- /dev/null +++ b/nvitop/tui/library/curses/ascii.py @@ -0,0 +1,213 @@ +"""Constants and membership tests for ASCII characters""" + +# Copied from the CPython repository. +# https://github.com/python/cpython/blob/HEAD/Lib/curses/ascii.py + +# pylint: disable=missing-function-docstring + +from __future__ import annotations + +from typing import overload + + +NUL = 0x00 # ^@ +SOH = 0x01 # ^A +STX = 0x02 # ^B +ETX = 0x03 # ^C +EOT = 0x04 # ^D +ENQ = 0x05 # ^E +ACK = 0x06 # ^F +BEL = 0x07 # ^G +BS = 0x08 # ^H +TAB = 0x09 # ^I +HT = 0x09 # ^I +LF = 0x0A # ^J +NL = 0x0A # ^J +VT = 0x0B # ^K +FF = 0x0C # ^L +CR = 0x0D # ^M +SO = 0x0E # ^N +SI = 0x0F # ^O +DLE = 0x10 # ^P +DC1 = 0x11 # ^Q +DC2 = 0x12 # ^R +DC3 = 0x13 # ^S +DC4 = 0x14 # ^T +NAK = 0x15 # ^U +SYN = 0x16 # ^V +ETB = 0x17 # ^W +CAN = 0x18 # ^X +EM = 0x19 # ^Y +SUB = 0x1A # ^Z +ESC = 0x1B # ^[ +FS = 0x1C # ^\ +GS = 0x1D # ^] +RS = 0x1E # ^^ +US = 0x1F # ^_ +SP = 0x20 # space +DEL = 0x7F # delete + +controlnames = [ + 'NUL', + 'SOH', + 'STX', + 'ETX', + 'EOT', + 'ENQ', + 'ACK', + 'BEL', + 'BS', + 'HT', + 'LF', + 'VT', + 'FF', + 'CR', + 'SO', + 'SI', + 'DLE', + 'DC1', + 'DC2', + 'DC3', + 'DC4', + 'NAK', + 'SYN', + 'ETB', + 'CAN', + 'EM', + 'SUB', + 'ESC', + 'FS', + 'GS', + 'RS', + 'US', + 'SP', +] + + +def _ctoi(c: int | str) -> int: + if isinstance(c, str): + return ord(c) + return c + + +def isalnum(c: int | str) -> bool: + return isalpha(c) or isdigit(c) + + +def isalpha(c: int | str) -> bool: + return isupper(c) or islower(c) + + +def isascii(c: int | str) -> bool: + return 0 <= _ctoi(c) <= 127 + + +def isblank(c: int | str) -> bool: + return _ctoi(c) in (9, 32) + + +def iscntrl(c: int | str) -> bool: + return 0 <= _ctoi(c) <= 31 or _ctoi(c) == 127 + + +def isdigit(c: int | str) -> bool: + return 48 <= _ctoi(c) <= 57 + + +def isgraph(c: int | str) -> bool: + return 33 <= _ctoi(c) <= 126 + + +def islower(c: int | str) -> bool: + return 97 <= _ctoi(c) <= 122 + + +def isprint(c: int | str) -> bool: + return 32 <= _ctoi(c) <= 126 + + +def ispunct(c: int | str) -> bool: + return isgraph(c) and not isalnum(c) + + +def isspace(c: int | str) -> bool: + return _ctoi(c) in (9, 10, 11, 12, 13, 32) + + +def isupper(c: int | str) -> bool: + return 65 <= _ctoi(c) <= 90 + + +def isxdigit(c: int | str) -> bool: + return isdigit(c) or (65 <= _ctoi(c) <= 70) or (97 <= _ctoi(c) <= 102) + + +def isctrl(c: int | str) -> bool: + return 0 <= _ctoi(c) < 32 + + +def ismeta(c: int | str) -> bool: + return _ctoi(c) > 127 + + +@overload +def ascii(c: int) -> int: ... # pylint: disable=redefined-builtin + + +@overload +def ascii(c: str) -> str: ... + + +def ascii(c: int | str) -> int | str: + if isinstance(c, str): + return chr(_ctoi(c) & 0x7F) + return _ctoi(c) & 0x7F + + +@overload +def ctrl(c: int) -> int: ... + + +@overload +def ctrl(c: str) -> str: ... + + +def ctrl(c: int | str) -> int | str: + if isinstance(c, str): + return chr(_ctoi(c) & 0x1F) + return _ctoi(c) & 0x1F + + +@overload +def alt(c: int) -> int: ... + + +@overload +def alt(c: str) -> str: ... + + +def alt(c: int | str) -> int | str: + if isinstance(c, str): + return chr(_ctoi(c) | 0x80) + return _ctoi(c) | 0x80 + + +@overload +def unctrl(c: int) -> int: ... + + +@overload +def unctrl(c: str) -> str: ... + + +def unctrl(c: int | str) -> int | str: + bits = _ctoi(c) + if bits == 0x7F: + rep = '^?' + elif isprint(bits & 0x7F): + rep = chr(bits & 0x7F) + else: + rep = '^' + chr(((bits & 0x7F) | 0x20) + 0x20) + if bits & 0x80: + return '!' + rep + return rep diff --git a/nvitop/tui/library/keybinding.py b/nvitop/tui/library/keybinding.py index 9d8d3924..ba5171cb 100644 --- a/nvitop/tui/library/keybinding.py +++ b/nvitop/tui/library/keybinding.py @@ -5,11 +5,11 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring import copy -import curses -import curses.ascii import string from collections import OrderedDict +from nvitop.tui.library import curses + DIGITS = set(map(ord, string.digits)) diff --git a/nvitop/tui/library/libcurses.py b/nvitop/tui/library/libcurses.py index f3fe963c..8b96faa2 100644 --- a/nvitop/tui/library/libcurses.py +++ b/nvitop/tui/library/libcurses.py @@ -5,11 +5,11 @@ import colorsys import contextlib -import curses import locale import os import signal +from nvitop.tui.library import curses from nvitop.tui.library.history import GRAPH_SYMBOLS @@ -161,15 +161,16 @@ def libcurses(colorful=False, light_theme=False): # Push a Ctrl+C (ascii value 3) to the curses getch stack def interrupt_handler(signalnum, frame): # pylint: disable=unused-argument - curses.ungetch(3) + curses.ungetch(curses.ascii.ETX) # Simulate a ^C press in curses when an interrupt is caught - signal.signal(signal.SIGINT, interrupt_handler) + original_interrupt_handler = signal.signal(signal.SIGINT, interrupt_handler) try: yield win finally: curses.endwin() + signal.signal(signal.SIGINT, original_interrupt_handler) class CursesShortcuts: diff --git a/nvitop/tui/library/messagebox.py b/nvitop/tui/library/messagebox.py index a8da9557..4a2e0b78 100644 --- a/nvitop/tui/library/messagebox.py +++ b/nvitop/tui/library/messagebox.py @@ -4,12 +4,12 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring -import curses import string import threading import time from functools import partial +from nvitop.tui.library import curses from nvitop.tui.library.displayable import Displayable from nvitop.tui.library.keybinding import normalize_keybinding from nvitop.tui.library.process import host diff --git a/nvitop/tui/library/mouse.py b/nvitop/tui/library/mouse.py index e6d6b130..0babfefe 100644 --- a/nvitop/tui/library/mouse.py +++ b/nvitop/tui/library/mouse.py @@ -4,7 +4,7 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring -import curses +from nvitop.tui.library import curses class MouseEvent: diff --git a/nvitop/tui/tui.py b/nvitop/tui/tui.py index bce13b25..11843235 100644 --- a/nvitop/tui/tui.py +++ b/nvitop/tui/tui.py @@ -3,11 +3,10 @@ # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring -import curses import shutil import time -from nvitop.tui.library import ALT_KEY, DisplayableContainer, KeyBuffer, KeyMaps, MouseEvent +from nvitop.tui.library import ALT_KEY, DisplayableContainer, KeyBuffer, KeyMaps, MouseEvent, curses from nvitop.tui.screens import ( BreakLoop, EnvironScreen,