From d8a47ac6ca40d229b9e00a3d2caa728f12650383 Mon Sep 17 00:00:00 2001 From: Xuehai Pan Date: Sun, 4 Sep 2022 00:23:41 +0800 Subject: [PATCH] feat(gui): support process tagging in main screen Signed-off-by: Xuehai Pan --- nvitop/gui/screens/main/__init__.py | 23 ++++++---- nvitop/gui/screens/main/process.py | 18 +++++--- nvitop/gui/screens/main/utils.py | 67 ++++++++++++++++++----------- nvitop/gui/top.py | 1 + 4 files changed, 69 insertions(+), 40 deletions(-) diff --git a/nvitop/gui/screens/main/__init__.py b/nvitop/gui/screens/main/__init__.py index 3f9b89f..2c89384 100644 --- a/nvitop/gui/screens/main/__init__.py +++ b/nvitop/gui/screens/main/__init__.py @@ -173,6 +173,9 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att self.root.update_size() self.root.need_redraw = True + def screen_move(direction): + self.move(direction) + def host_left(): self.process_panel.host_offset -= 2 @@ -191,8 +194,9 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att def select_clear(): self.selected.clear() - def screen_move(direction): - self.move(direction) + def tag(): + self.selected.tag() + select_move(direction=+1) def terminate(): self.selected.terminate() @@ -227,6 +231,13 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att self.root.keymaps.copy('main', 'r', '') self.root.keymaps.copy('main', 'r', '') + self.root.keymaps.bind('main', '', partial(screen_move, direction=-1)) + self.root.keymaps.copy('main', '', '[') + self.root.keymaps.copy('main', '', '') + self.root.keymaps.bind('main', '', partial(screen_move, direction=+1)) + self.root.keymaps.copy('main', '', ']') + self.root.keymaps.copy('main', '', '') + self.root.keymaps.bind('main', '', host_left) self.root.keymaps.copy('main', '', '') self.root.keymaps.bind('main', '', host_right) @@ -244,13 +255,7 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att self.root.keymaps.bind('main', '', partial(select_move, direction=-(1 << 20))) self.root.keymaps.bind('main', '', partial(select_move, direction=+(1 << 20))) self.root.keymaps.bind('main', '', select_clear) - - self.root.keymaps.bind('main', '', partial(screen_move, direction=-1)) - self.root.keymaps.copy('main', '', '[') - self.root.keymaps.copy('main', '', '') - self.root.keymaps.bind('main', '', partial(screen_move, direction=+1)) - self.root.keymaps.copy('main', '', ']') - self.root.keymaps.copy('main', '', '') + self.root.keymaps.bind('main', '', tag) self.root.keymaps.bind('main', 'T', terminate) self.root.keymaps.bind('main', 'K', kill) diff --git a/nvitop/gui/screens/main/process.py b/nvitop/gui/screens/main/process.py index de61079..d9bf401 100644 --- a/nvitop/gui/screens/main/process.py +++ b/nvitop/gui/screens/main/process.py @@ -227,7 +227,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes if self.selected.is_set(): identity = self.selected.identity - self.selected.clear() + self.selected.reset() for i, process in enumerate(snapshots): if process._ident == identity: # pylint: disable=protected-access self.selected.index = i @@ -422,7 +422,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes self.color_at(self.y + 3, self.x + offset + column_width - 1, width=1, attr='bold') if self.y_mouse is not None: - self.selected.clear() + self.selected.reset() self.selected.within_window = False if len(self.snapshots) > 0: @@ -476,7 +476,11 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes if self.selected.is_same(process): self.color_at( - y, self.x + 1, width=self.width - 2, fg='cyan', attr='bold | reverse' + y, + self.x + 1, + width=self.width - 2, + fg='yellow' if self.selected.is_tagged(process) else 'cyan', + attr='bold | reverse', ) self.selected.within_window = ( self.root.y <= y < self.root.termsize[0] and self.width >= 79 @@ -485,8 +489,10 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes if self.selected.is_same_on_host(process): self.addstr(y, self.x + 1, '=', self.get_fg_bg_attr(attr='bold | blink')) self.color_at(y, self.x + 2, width=3, fg=color) - if str(process.username) != USERNAME and not SUPERUSER: - self.color_at(y, self.x + 5, width=self.width - 6, attr='dim') + if self.selected.is_tagged(process): + self.color_at(y, self.x + 5, width=self.width - 6, fg='yellow', attr='bold') + elif str(process.username) != USERNAME and not SUPERUSER: + self.color_at(y, self.x + 5, width=self.width - 6, attr='dim') if is_zombie or no_permissions: self.color_at(y, self.x + 38 + command_offset, width=14, fg='yellow') elif is_gone: @@ -498,7 +504,7 @@ class ProcessPanel(Displayable): # pylint: disable=too-many-instance-attributes self.addstr(self.y + 5, self.x, '│ {} │'.format(message.ljust(self.width - 4))) text_offset = self.x + self.width - 47 - if self.selected.owned() and self.selected.within_window: + if len(self.selected.tagged) > 0 or (self.selected.owned() and self.selected.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') diff --git a/nvitop/gui/screens/main/utils.py b/nvitop/gui/screens/main/utils.py index 604e8ea..691dd82 100644 --- a/nvitop/gui/screens/main/utils.py +++ b/nvitop/gui/screens/main/utils.py @@ -6,12 +6,15 @@ import signal import time from collections import namedtuple +from operator import xor +from weakref import WeakValueDictionary from nvitop.gui.library import LARGE_INTEGER, NA, SUPERUSER, USERNAME, Snapshot, host class Selected: def __init__(self, panel): + self.tagged = WeakValueDictionary() self.panel = panel self.index = None self.within_window = True @@ -86,16 +89,35 @@ class Selected: return True return self.username == USERNAME - def send_signal(self, sig): - if self.owned() and self.within_window: + def tag(self): + if self.is_set(): try: - self.process.send_signal(sig) + del self.tagged[self.pid] + except KeyError: + self.tagged[self.pid] = self.process + + def foreach(self, func): + if len(self.tagged) > 0: + processes = tuple(self.tagged.values()) + elif self.owned() and self.within_window: + processes = (self.process,) + else: + return + + for process in processes: + try: + func(process) except host.PsutilError: pass else: - time.sleep(0.5) - if not self.process.is_running(): - self.clear() + flag = True + + if flag: + time.sleep(0.5) + self.clear() + + def send_signal(self, sig): + self.foreach(lambda process: process.send_signal(sig)) def interrupt(self): try: @@ -108,29 +130,21 @@ class Selected: pass def terminate(self): - if self.owned() and self.within_window: - try: - self.process.terminate() - except host.PsutilError: - pass - else: - time.sleep(0.5) - self.clear() + self.foreach(lambda process: process.terminate()) def kill(self): - if self.owned() and self.within_window: - try: - self.process.kill() - except host.PsutilError: - pass - else: - time.sleep(0.5) - self.clear() + self.foreach(lambda process: process.kill()) + + def reset(self): + self.index = None + self.within_window = True + self._process = None + self._username = None + self._ident = None def clear(self): - self.__init__(self.panel) # pylint: disable=unnecessary-dunder-call - - reset = clear + self.tagged.clear() + self.reset() def is_set(self): return self.process is not None @@ -155,5 +169,8 @@ class Selected: return False + def is_tagged(self, process): + return process.pid in self.tagged + Order = namedtuple('Order', ['key', 'reverse', 'offset', 'column', 'previous', 'next']) diff --git a/nvitop/gui/top.py b/nvitop/gui/top.py index 1adcdb9..3fcdd8e 100644 --- a/nvitop/gui/top.py +++ b/nvitop/gui/top.py @@ -252,6 +252,7 @@ class Top(DisplayableContainer): # pylint: disable=too-many-instance-attributes if not self.treeview_screen.selected.is_set(): self.treeview_screen.selected.process = self.main_screen.selected.process + self.main_screen.selected.clear() def show_environ(): show_screen(self.environ_screen, focused=True)