feat(cli): add option --readonly to CLI (#214)
Some checks failed
Build / Build sdist and wheels (push) Has been cancelled
Lint / lint (push) Has been cancelled
Build / Publish to PyPI (push) Has been cancelled

This commit is contained in:
Xuehai Pan 2026-05-06 18:33:10 +08:00 committed by GitHub
parent 4e814c52a6
commit 8561956c12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 209 additions and 154 deletions

View file

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `nvidia-ml-py` 13.595.45 to support list.
- Add support for open kernel-module driver packages (e.g., `nvidia-driver-595-open`) in `install-nvidia-driver.sh` with new `--proprietary` and `--open` flags by [@XuehaiPan](https://github.com/XuehaiPan).
- Add TLS and mutual TLS (mTLS) support for `nvitop-exporter` via new `--certfile`, `--keyfile`, `--client-cafile`, `--client-capath`, and `--client-auth-required` CLI flags by [@XuehaiPan](https://github.com/XuehaiPan) in [#213](https://github.com/XuehaiPan/nvitop/pull/213). Issued by [@StefanSander3](https://github.com/StefanSander3) in [#131](https://github.com/XuehaiPan/nvitop/issues/131).
- Add `--readonly` CLI flag (and equivalent `NVITOP_MONITOR_MODE="readonly"` env token) for monitor mode that disables all process-mutating shortcuts (`Ctrl-c` / `T` / `K` / `I` / `k`) by [@XuehaiPan](https://github.com/XuehaiPan).
### Changed

View file

@ -292,6 +292,8 @@ You can configure the default monitor mode with the `NVITOP_MONITOR_MODE` enviro
In monitor mode, you can use <kbd>Ctrl-c</kbd> / <kbd>T</kbd> / <kbd>K</kbd> keys to interrupt / terminate / kill a process. And it's recommended to *terminate* or *kill* a process in the **tree-view screen** (shortcut: <kbd>t</kbd>). For normal users, `nvitop` will shallow other users' processes (in low-intensity colors). For **system administrators**, you can use `sudo nvitop` to terminate other users' processes.
To run `nvitop` as a viewer only and disable all process-mutating shortcuts, pass `--readonly` (or set `NVITOP_MONITOR_MODE="readonly"`). The signal keys above become no-ops, the on-screen "Press ^C(INT)/T(TERM)/K(KILL) to send signals" hint is hidden, and the corresponding rows in the help screen are dimmed. Use this when sharing a session over SSH, demoing on a multi-tenant box, or wrapping `nvitop` in a non-admin alias.
Also, to enter the process metrics screen, select a process and then press the <kbd>Enter</kbd> / <kbd>Return</kbd> key . `nvitop` dynamically displays the process metrics with live graphs.
<p align="center">
@ -343,11 +345,11 @@ Type `nvitop --help` for more command options:
```text
usage: nvitop [--help] [--version] [--once | --monitor [{auto,full,compact}]]
[--interval SEC] [--ascii] [--colorful] [--force-color] [--light]
[--gpu-util-thresh th1 th2] [--mem-util-thresh th1 th2]
[--only INDEX [INDEX ...]] [--only-visible]
[--compute] [--only-compute] [--graphics] [--only-graphics]
[--user [USERNAME ...]] [--pid PID [PID ...]]
[--interval SEC] [--no-unicode] [--readonly] [--colorful]
[--force-color] [--light] [--gpu-util-thresh th1 th2]
[--mem-util-thresh th1 th2] [--only INDEX [INDEX ...]]
[--only-visible] [--compute] [--only-compute] [--graphics]
[--only-graphics] [--user [USERNAME ...]] [--pid PID [PID ...]]
An interactive NVIDIA-GPU process viewer.
@ -355,19 +357,22 @@ options:
--help, -h Show this help message and exit.
--version, -V Show nvitop's version number and exit.
--once, -1 Report query data only once.
--monitor [{auto,full,compact}], -m [{auto,full,compact}]
--monitor, -m [{auto,full,compact}]
Run as a resource monitor. Continuously report query data and handle user inputs.
If the argument is omitted, the value from `NVITOP_MONITOR_MODE` will be used.
(default fallback mode: auto)
--interval SEC Process status update interval in seconds. (default: 2)
--ascii, --no-unicode, -U
--no-unicode, --ascii, -U
Use ASCII characters only, which is useful for terminals without Unicode support.
--readonly Disable all system and process changing features (e.g., terminating processes).
Set variable `NVITOP_MONITOR_MODE="readonly"` for convenience.
coloring:
--colorful Use gradient colors to get spectrum-like bar charts. This option is only available
when the terminal supports 256 colors. You may need to set environment variable
`TERM="xterm-256color"`. Note that the terminal multiplexer, such as `tmux`, may
override the `TERM` variable.
--colorful Use gradient colors to get spectrum-like bar charts.
Set variable `NVITOP_MONITOR_MODE="colorful"` for convenience.
This option is only available when the terminal supports 256 colors.
You may need to set environment variable `TERM="xterm-256color"`. Note that the
terminal multiplexer, such as `tmux`, may override the `TERM` variable.
--force-color Force colorize even when `stdout` is not a TTY terminal.
--light Tweak visual results for light theme terminals in monitor mode.
Set variable `NVITOP_MONITOR_MODE="light"` on light terminals for convenience.
@ -381,7 +386,7 @@ coloring:
( 1 <= th1 < th2 <= 99, defaults: 10 80 )
device filtering:
--only INDEX [INDEX ...], -o INDEX [INDEX ...]
--only, -o INDEX [INDEX ...]
Only show the specified devices, suppress option `--only-visible`.
--only-visible, -ov Only show devices in the `CUDA_VISIBLE_DEVICES` environment variable.
@ -390,9 +395,9 @@ process filtering:
--only-compute, -C Only show GPU processes exactly with the compute context. (type: 'C' only)
--graphics, -g Only show GPU processes with the graphics context. (type: 'G' or 'C+G')
--only-graphics, -G Only show GPU processes exactly with the graphics context. (type: 'G' only)
--user [USERNAME ...], -u [USERNAME ...]
--user, -u [USERNAME ...]
Only show processes of the given users (or `$USER` for no argument).
--pid PID [PID ...], -p PID [PID ...]
--pid, -p PID [PID ...]
Only show processes of the given PIDs.
```
@ -400,7 +405,7 @@ process filtering:
| Name | Description | Valid Values | Default Value |
| -------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- | ----------------- |
| `NVITOP_MONITOR_MODE` | The default display mode (a comma-separated string) | `auto` / `full` / `compact`<br>`plain` / `colorful`<br>`dark` / `light` | `auto,plain,dark` |
| `NVITOP_MONITOR_MODE` | The default display mode (a comma-separated string) | `auto` / `full` / `compact`<br>`plain` / `colorful`<br>`dark` / `light`<br>`readonly` (disables process-mutating shortcuts) | `auto,plain,dark` |
| `NVITOP_GPU_UTILIZATION_THRESHOLDS` | Thresholds of GPU utilization | `10,75` , `1,99`, ... | `10,75` |
| `NVITOP_MEMORY_UTILIZATION_THRESHOLDS` | Thresholds of GPU memory percent | `10,80` , `1,99`, ... | `10,80` |
| `LOGLEVEL` | Log level for log messages | `DEBUG` , `INFO`, `WARNING`, ... | `WARNING` |
@ -450,9 +455,9 @@ echo 'set -gx NVITOP_MONITOR_MODE "full"' >> ~/.config/fish/config.fish
| | |
| `<Space>` | Tag/untag current process. |
| `<Esc>` | Clear process selection. |
| `<C-c>`<br>`I` | Send `signal.SIGINT` to the selected process (interrupt). |
| `T` | Send `signal.SIGTERM` to the selected process (terminate). |
| `K` | Send `signal.SIGKILL` to the selected process (kill). |
| `<C-c>`<br>`I` | Send `signal.SIGINT` to the selected process (interrupt). *(disabled under `--readonly`)* |
| `T` | Send `signal.SIGTERM` to the selected process (terminate). *(disabled under `--readonly`)* |
| `K` | Send `signal.SIGKILL` to the selected process (kill). *(disabled under `--readonly`)* |
| | |
| `e` | Show process environment. |
| `t` | Toggle tree-view screen. |

View file

@ -102,6 +102,15 @@ def parse_arguments() -> argparse.Namespace:
action='store_true',
help='Use ASCII characters only, which is useful for terminals without Unicode support.',
)
parser.add_argument(
'--readonly',
dest='readonly',
action='store_true',
help=(
'Disable all system and process changing features (e.g., terminating processes).\n'
'Set variable `NVITOP_MONITOR_MODE="readonly"` for convenience.'
),
)
coloring = parser.add_argument_group('coloring')
coloring.add_argument(
@ -109,10 +118,11 @@ def parse_arguments() -> argparse.Namespace:
dest='colorful',
action='store_true',
help=(
'Use gradient colors to get spectrum-like bar charts. This option is only available\n'
'when the terminal supports 256 colors. You may need to set environment variable\n'
'`TERM="xterm-256color"`. Note that the terminal multiplexer, such as `tmux`, may\n'
'override the `TERM` variable.'
'Use gradient colors to get spectrum-like bar charts.\n'
'Set variable `NVITOP_MONITOR_MODE="colorful"` for convenience.\n'
'This option is only available when the terminal supports 256 colors.\n'
'You may need to set environment variable `TERM="xterm-256color"`. Note that the\n'
'terminal multiplexer, such as `tmux`, may override the `TERM` variable.'
),
)
coloring.add_argument(
@ -234,6 +244,8 @@ def parse_arguments() -> argparse.Namespace:
args.colorful = 'colorful' in NVITOP_MONITOR_MODE and 'plain' not in NVITOP_MONITOR_MODE
if not args.light:
args.light = 'light' in NVITOP_MONITOR_MODE and 'dark' not in NVITOP_MONITOR_MODE
if not args.readonly:
args.readonly = 'readonly' in NVITOP_MONITOR_MODE
if args.user is not None and len(args.user) == 0:
args.user.append(USERNAME)
if args.gpu_util_thresh is None:
@ -355,6 +367,7 @@ def main() -> int:
no_unicode=args.no_unicode,
mode=args.monitor,
interval=args.interval,
readonly=args.readonly,
win=win,
)
tui.loop()
@ -364,7 +377,7 @@ def main() -> int:
messages.append(f'ERROR: Failed to initialize `curses` ({ex})')
if tui is None:
tui = TUI(devices, filters, no_unicode=args.no_unicode)
tui = TUI(devices, filters, no_unicode=args.no_unicode, readonly=args.readonly)
if not sys.stdout.isatty():
parent = HostProcess().parent()
if parent is not None:

View file

@ -18,7 +18,12 @@ from nvitop.tui.library.keybinding import (
normalize_keybinding,
)
from nvitop.tui.library.libcurses import libcurses, setlocale_utf8
from nvitop.tui.library.messagebox import MessageBox
from nvitop.tui.library.messagebox import (
SIGNAL_HINT_BLANK,
SIGNAL_HINT_KEY_SPANS,
SIGNAL_HINT_TEXT,
MessageBox,
)
from nvitop.tui.library.mouse import MouseEvent
from nvitop.tui.library.process import GpuProcess, HostProcess
from nvitop.tui.library.selection import Selection
@ -58,6 +63,9 @@ __all__ = [
'NA',
'PASSIVE_ACTION',
'QUANT_KEY',
'SIGNAL_HINT_BLANK',
'SIGNAL_HINT_KEY_SPANS',
'SIGNAL_HINT_TEXT',
'SPECIAL_KEYS',
'USERNAME',
'USER_CONTEXT',

View file

@ -11,7 +11,7 @@ import string
import threading
import time
from functools import partial
from typing import TYPE_CHECKING, Literal
from typing import TYPE_CHECKING, ClassVar, Literal
from nvitop.tui.library import host
from nvitop.tui.library.displayable import Displayable
@ -28,11 +28,30 @@ if TYPE_CHECKING:
from nvitop.tui.tui import TUI
__all__ = ['MessageBox']
__all__ = ['SIGNAL_HINT_BLANK', 'SIGNAL_HINT_KEY_SPANS', 'SIGNAL_HINT_TEXT', 'MessageBox']
DIGITS: frozenset[str] = frozenset(string.digits)
SignalHintSpanKind = Literal['key', 'label']
# Single source of truth for the on-screen hint that surfaces the kill/terminate/interrupt
# bindings. The process panel and the tree-view screen both render this string with
# screen-specific coloring; they share these positions and widths so the two layouts stay in
# lockstep when the wording is updated.
SIGNAL_HINT_TEXT: str = '(Press ^C(INT)/T(TERM)/K(KILL) to send signals)'
SIGNAL_HINT_BLANK: str = ' ' * len(SIGNAL_HINT_TEXT)
# (kind, offset_within_text, width) — ``kind`` is ``'key'`` for the shortcut tokens
# (``^C``, ``T``, ``K``) and ``'label'`` for the signal names (``INT``, ``TERM``, ``KILL``).
SIGNAL_HINT_KEY_SPANS: tuple[tuple[SignalHintSpanKind, int, int], ...] = (
('key', 7, 2),
('label', 10, 3),
('key', 15, 1),
('label', 17, 4),
('key', 23, 1),
('label', 25, 4),
)
class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes
class Option: # pylint: disable=too-few-public-methods
@ -302,12 +321,45 @@ class MessageBox(Displayable): # pylint: disable=too-many-instance-attributes
keymaps.alias('messagebox', '<Left>', '<S-Tab>')
keymaps.alias('messagebox', '<Right>', '<Tab>')
# Keybindings that route into `confirm_sending_signal_to_processes`. Centralized here so
# every selectable screen can register the same bindings without duplicating the
# `signal -> primary key + aliases` mapping.
_SIGNAL_KEYBINDINGS: ClassVar[
tuple[tuple[Literal['terminate', 'kill', 'interrupt'], str, tuple[str, ...]], ...]
] = (
('terminate', 'T', ()),
('kill', 'K', ('k',)),
('interrupt', '<C-c>', ('I',)),
)
@classmethod
def register_signal_keybindings(cls, screen: BaseSelectableScreen, keymap_name: str) -> None:
"""Bind the kill/terminate/interrupt keys for ``screen`` under ``keymap_name``.
The bindings are skipped entirely when the selection is read-only so the
``--readonly`` contract is honored at the keybinding layer in addition to the
:meth:`Selection` boundary.
"""
if screen.selection.readonly:
return
keymaps = screen.root.keymaps
for signal, key, aliases in cls._SIGNAL_KEYBINDINGS:
keymaps.bind(
keymap_name,
key,
partial(cls.confirm_sending_signal_to_processes, signal=signal, screen=screen),
)
for alias in aliases:
keymaps.alias(keymap_name, key, alias)
@staticmethod
def confirm_sending_signal_to_processes(
signal: Literal['terminate', 'kill', 'interrupt'],
screen: BaseSelectableScreen,
) -> None:
assert signal in ('terminate', 'kill', 'interrupt')
if screen.selection.readonly:
return
default = {'terminate': 0, 'kill': 1, 'interrupt': 2}.get(signal)
processes = []
for process in screen.selection.processes():

View file

@ -5,9 +5,10 @@
from __future__ import annotations
import functools
import signal
import time
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, TypeVar
from weakref import WeakValueDictionary
from nvitop.api import NA, Snapshot
@ -25,7 +26,27 @@ if TYPE_CHECKING:
__all__ = ['Selection']
class Selection: # pylint: disable=too-many-instance-attributes
_F = TypeVar('_F', bound='Callable[..., None]')
def _writable_only(method: _F) -> _F:
"""Defense-in-depth: skip the call when the bound root is read-only.
Closes the bypass where a dialog callback captured before the flag was flipped, or
any caller holding a reference to one of these methods, would otherwise route
around the keybinding and `MessageBox` guards.
"""
@functools.wraps(method)
def wrapper(self: Selection, *args: object, **kwargs: object) -> None:
if self.readonly:
return
method(self, *args, **kwargs)
return wrapper # type: ignore[return-value]
class Selection: # pylint: disable=too-many-instance-attributes,too-many-public-methods
def __init__(self, displayable: Displayable) -> None:
self.tagged: WeakValueDictionary[int, GpuProcess | HostProcess] = WeakValueDictionary()
self.displayable: Displayable = displayable
@ -114,6 +135,15 @@ class Selection: # pylint: disable=too-many-instance-attributes
return (self.process,) # type: ignore[return-value]
return ()
def has_actionable_processes(self) -> bool:
"""Whether the selection currently identifies at least one process to send signals to.
Mirrors the visibility predicate of the on-screen
``Press ^C(INT)/T(TERM)/K(KILL) to send signals`` hint: ``True`` if any process is
tagged, or if the focused process is owned by the user and within the visible window.
"""
return len(self.tagged) > 0 or (self.owned() and self.within_window)
def foreach(self, func: Callable[[GpuProcess | HostProcess], None]) -> None:
flag = False
for process in self.processes():
@ -128,9 +158,15 @@ class Selection: # pylint: disable=too-many-instance-attributes
time.sleep(0.25)
self.clear()
@property
def readonly(self) -> bool:
return bool(self.displayable.root.readonly) # type: ignore[union-attr]
@_writable_only
def send_signal(self, sig: int) -> None:
self.foreach(lambda process: process.send_signal(sig))
@_writable_only
def interrupt(self) -> None:
try:
# pylint: disable-next=no-member
@ -138,9 +174,11 @@ class Selection: # pylint: disable=too-many-instance-attributes
except SystemError:
pass
@_writable_only
def terminate(self) -> None:
self.foreach(lambda process: process.terminate())
@_writable_only
def kill(self) -> None:
self.foreach(lambda process: process.kill())

View file

@ -85,6 +85,11 @@ class HelpScreen(BaseScreen): # pylint: disable=too-many-instance-attributes
**dict.fromkeys(range(24, 29), ('blue', 'blue')),
29: ('magenta', 'magenta'),
}
# Rows whose right-hand action column is colored red are exactly the
# kill/terminate/interrupt keybindings — the same set ``--readonly`` disables.
self.readonly_disabled_rows: frozenset[int] = frozenset(
row for row, (_, right) in self.color_matrix.items() if right == 'red'
)
self.x, self.y = root.x, root.y
self.width: int = max(map(len, self.infos))
@ -119,9 +124,13 @@ class HelpScreen(BaseScreen): # pylint: disable=too-many-instance-attributes
for dy, (left, right) in self.color_matrix.items():
if left is not None:
self.color_at(self.y + dy, self.x, width=12, fg=left, attr='bold')
if right is not None:
if right is not None and not (self.root.readonly and dy in self.readonly_disabled_rows):
self.color_at(self.y + dy, self.x + 39, width=13, fg=right, attr='bold')
if self.root.readonly:
for dy in self.readonly_disabled_rows:
self.color_at(self.y + dy, self.x + 39, width=self.width - 39, attr='dim')
def press(self, key: int) -> bool:
self.root.keymaps.use_keymap('help')
return self.root.press(key)

View file

@ -295,35 +295,7 @@ class MainScreen(BaseSelectableScreen): # pylint: disable=too-many-instance-att
keymaps.bind('main', '<Esc>', select_clear)
keymaps.bind('main', '<Space>', tag)
keymaps.bind(
'main',
'T',
partial(
MessageBox.confirm_sending_signal_to_processes,
signal='terminate',
screen=self,
),
)
keymaps.bind(
'main',
'K',
partial(
MessageBox.confirm_sending_signal_to_processes,
signal='kill',
screen=self,
),
)
keymaps.alias('main', 'K', 'k')
keymaps.bind(
'main',
'<C-c>',
partial(
MessageBox.confirm_sending_signal_to_processes,
signal='interrupt',
screen=self,
),
)
keymaps.alias('main', '<C-c>', 'I')
MessageBox.register_signal_keybindings(self, keymap_name='main')
keymaps.bind('main', ',', order_previous)
keymaps.alias('main', ',', '<')

View file

@ -18,6 +18,9 @@ from nvitop.tui.library import (
IS_WINDOWS,
IS_WSL,
LARGE_INTEGER,
SIGNAL_HINT_BLANK,
SIGNAL_HINT_KEY_SPANS,
SIGNAL_HINT_TEXT,
USER_CONTEXT,
USERNAME,
Device,
@ -632,19 +635,25 @@ class ProcessPanel(BaseSelectablePanel): # pylint: disable=too-many-instance-at
message = ' No running processes found{} '.format(' (in WSL)' if IS_WSL else '')
self.addstr(self.y + 5, self.x, f'{message.ljust(self.width - 4)}')
text_offset = self.x + self.width - 47
if len(self.selection.tagged) > 0 or (
self.selection.owned() and self.selection.within_window
):
self.addstr(self.y, text_offset, '(Press ^C(INT)/T(TERM)/K(KILL) to send signals)')
self.color_at(self.y, text_offset + 7, width=2, fg='magenta', attr='bold | italic')
self.color_at(self.y, text_offset + 10, width=3, fg='red', attr='bold')
self.color_at(self.y, text_offset + 15, width=1, fg='magenta', attr='bold | italic')
self.color_at(self.y, text_offset + 17, width=4, fg='red', attr='bold')
self.color_at(self.y, text_offset + 23, width=1, fg='magenta', attr='bold | italic')
self.color_at(self.y, text_offset + 25, width=4, fg='red', attr='bold')
else:
self.addstr(self.y, text_offset, ' ' * 47)
if self.root.readonly:
return
text_offset = self.x + self.width - len(SIGNAL_HINT_TEXT)
if not self.selection.has_actionable_processes():
self.addstr(self.y, text_offset, SIGNAL_HINT_BLANK)
return
self.addstr(self.y, text_offset, SIGNAL_HINT_TEXT)
for kind, dx, width in SIGNAL_HINT_KEY_SPANS:
if kind == 'key':
self.color_at(
self.y,
text_offset + dx,
width=width,
fg='magenta',
attr='bold | italic',
)
else:
self.color_at(self.y, text_offset + dx, width=width, fg='red', attr='bold')
def finalize(self) -> None:
self.y_mouse = None

View file

@ -16,6 +16,8 @@ from nvitop.tui.library import (
IS_SUPERUSER,
IS_WSL,
NA,
SIGNAL_HINT_KEY_SPANS,
SIGNAL_HINT_TEXT,
USERNAME,
Device,
GpuProcess,
@ -395,7 +397,7 @@ class TreeViewScreen(BaseSelectableScreen): # pylint: disable=too-many-instance
super().poke()
def draw(self) -> None: # pylint: disable=too-many-statements,too-many-locals
def draw(self) -> None: # pylint: disable=too-many-statements,too-many-locals,too-many-branches
self.color_reset()
pid_width = max(3, max((len(str(process.pid)) for process in self.snapshots), default=3))
@ -510,60 +512,32 @@ class TreeViewScreen(BaseSelectableScreen): # pylint: disable=too-many-instance
if not hint:
self.selection.clear()
if self.root.readonly or not self.selection.has_actionable_processes():
return
self.color(fg='cyan', attr='bold | reverse')
text_offset = self.x + self.width - 47
if len(self.selection.tagged) > 0 or (
self.selection.owned() and self.selection.within_window
):
self.addstr(self.y, text_offset - 1, ' (Press ^C(INT)/T(TERM)/K(KILL) to send signals)')
self.color_at(
self.y,
text_offset + 7,
width=2,
fg='cyan',
bg='yellow',
attr='bold | italic | reverse',
)
self.color_at(
self.y,
text_offset + 10,
width=3,
fg='cyan',
bg='red',
attr='bold | reverse',
)
self.color_at(
self.y,
text_offset + 15,
width=1,
fg='cyan',
bg='yellow',
attr='bold | italic | reverse',
)
self.color_at(
self.y,
text_offset + 17,
width=4,
fg='cyan',
bg='red',
attr='bold | reverse',
)
self.color_at(
self.y,
text_offset + 23,
width=1,
fg='cyan',
bg='yellow',
attr='bold | italic | reverse',
)
self.color_at(
self.y,
text_offset + 25,
width=4,
fg='cyan',
bg='red',
attr='bold | reverse',
)
text_offset = self.x + self.width - len(SIGNAL_HINT_TEXT)
# Leading space lets the cell share the row-above's reverse-highlighted background.
self.addstr(self.y, text_offset - 1, ' ' + SIGNAL_HINT_TEXT)
for kind, dx, width in SIGNAL_HINT_KEY_SPANS:
if kind == 'key':
self.color_at(
self.y,
text_offset + dx,
width=width,
fg='cyan',
bg='yellow',
attr='bold | italic | reverse',
)
else:
self.color_at(
self.y,
text_offset + dx,
width=width,
fg='cyan',
bg='red',
attr='bold | reverse',
)
def finalize(self) -> None:
self.y_mouse = None
@ -632,32 +606,4 @@ class TreeViewScreen(BaseSelectableScreen): # pylint: disable=too-many-instance
keymaps.bind('treeview', '<Esc>', select_clear)
keymaps.bind('treeview', '<Space>', tag)
keymaps.bind(
'treeview',
'T',
partial(
MessageBox.confirm_sending_signal_to_processes,
signal='terminate',
screen=self,
),
)
keymaps.bind(
'treeview',
'K',
partial(
MessageBox.confirm_sending_signal_to_processes,
signal='kill',
screen=self,
),
)
keymaps.alias('treeview', 'K', 'k')
keymaps.bind(
'treeview',
'<C-c>',
partial(
MessageBox.confirm_sending_signal_to_processes,
signal='interrupt',
screen=self,
),
)
keymaps.alias('treeview', '<C-c>', 'I')
MessageBox.register_signal_keybindings(self, keymap_name='treeview')

View file

@ -51,6 +51,7 @@ class TUI(DisplayableContainer[Union[BaseScreen, MessageBox]]): # pylint: disab
no_unicode: bool = False,
mode: MonitorMode = 'auto',
interval: float | None = None,
readonly: bool = False,
win: curses.window | None = None,
) -> None:
super().__init__(win, root=self)
@ -61,6 +62,7 @@ class TUI(DisplayableContainer[Union[BaseScreen, MessageBox]]): # pylint: disab
self.termsize: tuple[int, int] | None = None
self.no_unicode: bool = no_unicode
self.readonly: bool = readonly
self.devices: list[Device] = devices
self.device_count: int = len(self.devices)