feat(gui): support process tagging in main screen

Signed-off-by: Xuehai Pan <XuehaiPan@pku.edu.cn>
This commit is contained in:
Xuehai Pan 2022-09-04 00:23:41 +08:00
parent 011913880e
commit d8a47ac6ca
4 changed files with 69 additions and 40 deletions

View file

@ -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', '<C-r>')
self.root.keymaps.copy('main', 'r', '<F5>')
self.root.keymaps.bind('main', '<PageUp>', partial(screen_move, direction=-1))
self.root.keymaps.copy('main', '<PageUp>', '[')
self.root.keymaps.copy('main', '<PageUp>', '<A-K>')
self.root.keymaps.bind('main', '<PageDown>', partial(screen_move, direction=+1))
self.root.keymaps.copy('main', '<PageDown>', ']')
self.root.keymaps.copy('main', '<PageDown>', '<A-J>')
self.root.keymaps.bind('main', '<Left>', host_left)
self.root.keymaps.copy('main', '<Left>', '<A-h>')
self.root.keymaps.bind('main', '<Right>', host_right)
@ -244,13 +255,7 @@ class MainScreen(DisplayableContainer): # pylint: disable=too-many-instance-att
self.root.keymaps.bind('main', '<Home>', partial(select_move, direction=-(1 << 20)))
self.root.keymaps.bind('main', '<End>', partial(select_move, direction=+(1 << 20)))
self.root.keymaps.bind('main', '<Esc>', select_clear)
self.root.keymaps.bind('main', '<PageUp>', partial(screen_move, direction=-1))
self.root.keymaps.copy('main', '<PageUp>', '[')
self.root.keymaps.copy('main', '<PageUp>', '<A-K>')
self.root.keymaps.bind('main', '<PageDown>', partial(screen_move, direction=+1))
self.root.keymaps.copy('main', '<PageDown>', ']')
self.root.keymaps.copy('main', '<PageDown>', '<A-J>')
self.root.keymaps.bind('main', '<Space>', tag)
self.root.keymaps.bind('main', 'T', terminate)
self.root.keymaps.bind('main', 'K', kill)

View file

@ -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')

View file

@ -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'])

View file

@ -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)