Skip to content

Commit

Permalink
Fixed issue darxtrix#32 - separated keyboard shortcuts in a JSON file
Browse files Browse the repository at this point in the history
The shortcuts are now in a JSON file. The file contains the shortcut key,
the name of the method in Python and the name to show to the user.
  • Loading branch information
sebojanko committed Dec 2, 2018
1 parent 9bfc8bc commit dd90fc1
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 35 deletions.
10 changes: 10 additions & 0 deletions keybindings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
["^N", "sort_by_memory", "Memory Sort"],
["^T", "sort_by_time", "Time Sort"],
["^K", "kill_process", "Kill"],
["^Q", "quit", "Quit"],
["^R", "reset", "Reset"],
["^L", "show_detailed_process_info", "Process Info"],
["^F", "do_process_filtering_work", "Filter"],
["g", "", "Top"]
]
93 changes: 58 additions & 35 deletions ptop/interfaces/GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
'''
Graphical User Interface for ptop
'''

from collections import OrderedDict
import json
import inspect
import npyscreen, math, drawille
import psutil, logging, weakref, sys
from ptop.utils import ThreadJob
Expand All @@ -16,31 +18,34 @@
PREVIOUS_TERMINAL_WIDTH = None
PREVIOUS_TERMINAL_HEIGHT = None

KEYBINDINGS_FILE = 'keybindings.json'
shortcut_keys = OrderedDict()


class ProcessDetailsInfoBox(npyscreen.PopupWide):

'''
This widget is used to who the detailed information about the selected process
This widget is used to who the detailed information about the selected process
in the processes table. Curretly only the port information is shown for a selected
process.
` '''
#Set them to fix the position
SHOW_ATX = 0
SHOW_ATY = 0
DEFAULT_COLUMNS = PREVIOUS_TERMINAL_WIDTH

def __init__(self,local_ports,process_pid,open_files):
self._local_ports = local_ports
self._process_pid = process_pid
self._open_files = open_files
super(ProcessDetailsInfoBox,self).__init__()

def create(self,**kwargs):
'''
'''
Sub widgets [details_box_heading,details_box] are only shown in GUI if
they are created in the create() method
'''
super(ProcessDetailsInfoBox,self).create()

self._logger = logging.getLogger(__name__)
self._logger.info("Showing the local ports {0} and files opened and are being used by the process with pid {1}".format(self._local_ports,
str(self._process_pid)))
Expand Down Expand Up @@ -77,31 +82,31 @@ def adjust_widgets(self):

class ProcessFilterInputBox(npyscreen.Popup):
'''
Helper widget(input-box) that is used for filtering the processes list
Helper widget(input-box) that is used for filtering the processes list
on the basis of entered filtering string in the widget
'''
def create(self):
super(ProcessFilterInputBox, self).create()
self.filterbox = self.add(npyscreen.TitleText, name='Filter String:', )
self.nextrely += 1
self.statusline = self.add(npyscreen.Textfield, color = 'LABEL', editable = False)

def updatestatusline(self):
'''
This updates the status line that displays how many processes in the
This updates the status line that displays how many processes in the
processes list are matching to the filtering string
'''
self.owner_widget._filter = self.filterbox.value
total_matches = self.owner_widget.filter_processes()
if self.filterbox.value == None or self.filterbox.value == '':
self.statusline.value = ''
elif total_matches == 0:
elif total_matches == 0:
self.statusline.value = '(No Matches)'
elif total_matches == 1:
self.statusline.value = '(1 Match)'
else:
self.statusline.value = '(%s Matches)' % total_matches

def adjust_widgets(self):
'''
This method is called on any text change in filter box.
Expand All @@ -116,25 +121,40 @@ class CustomMultiLineAction(npyscreen.MultiLineAction):
'''
def __init__(self,*args,**kwargs):
super(CustomMultiLineAction,self).__init__(*args,**kwargs)
self.add_handlers({
"^N" : self._sort_by_memory,
"^T" : self._sort_by_time,
"^K" : self._kill_process,
"^Q" : self._quit,
"^R" : self._reset,
"^H" : self._do_process_filtering_work,
"^L" : self._show_detailed_process_info,
"^F" : self._do_process_filtering_work
})
self._filtering_flag = False
self._logger = logging.getLogger(__name__)
handlers = self._read_keybindings()
self.add_handlers(handlers)
'''
Non-sorted processes table entries, practically this will never be
None beacuse the user will ask for a certain process info only
after chossing one from the processes table
'''
self._uncurtailed_process_data = None

def _read_keybindings(self):
global shortcut_keys
keybindings = {}

with open(KEYBINDINGS_FILE) as f:
bindings = json.load(f)

methods = self._get_class_methods()

# we extract the methods by name and assign them to the key
for b in bindings:
if b[1]:
keybindings[b[0]] = methods["_" + b[1]]
shortcut_keys[b[0]] = b[2]

return keybindings

def _get_class_methods(self):
methods = {}
for m in inspect.getmembers(self, inspect.ismethod):
methods[m[0]] = m[1]
return methods

def _get_selected_process_pid(self):
'''
Parses the process pid from the selected line in the process
Expand All @@ -157,7 +177,7 @@ def _get_local_ports_used_by_a_process(self,process_pid):
for proc in self._uncurtailed_process_data:
if proc['id'] == process_pid:
return proc['local_ports']

def _get_list_of_open_files(self,process_pid):
"""
Given the Process ID, return the list of all the open files
Expand Down Expand Up @@ -207,7 +227,7 @@ def _show_detailed_process_info(self,*args,**kwargs):
open ports and the opened files list
"""
self._logger.info("Showing process details for the selected process")
# This is not working, local_ports information is not getting passed in the
# This is not working, local_ports information is not getting passed in the
# create method of self._logger = logging.getLogger(__name__)
# self.process_details_view_helper = ProcessDetailsInfoBox(local_ports=['1','2','3'])
process_pid = self._get_selected_process_pid()
Expand Down Expand Up @@ -236,10 +256,10 @@ def _quit(self,*args,**kwargs):

def filter_processes(self):
'''
This method is used to filter the processes in the processes table on the
This method is used to filter the processes in the processes table on the
basis of the filtering string entered in the filter box
When the user presses OK in the input box widget the value of the processes
table is set to **filtered** processes
When the user presses OK in the input box widget the value of the processes
table is set to **filtered** processes
It returns the count of the processes matched to the filtering string
'''
self._logger.info("Filtering processes on the basis of filter : {0}".format(self._filter))
Expand Down Expand Up @@ -280,14 +300,14 @@ class WindowForm(npyscreen.FormBaseNew):
'''
def create(self, *args, **kwargs):
super(WindowForm, self).create(*args, **kwargs)

def while_waiting(self):
pass


class PtopGUI(npyscreen.NPSApp):
'''
GUI class for ptop.
GUI class for ptop.
This controls the rendering of the main window and acts as the registering point
for all other widgets
'''
Expand All @@ -305,7 +325,7 @@ def __init__(self,statistics,stop_event,arg,sensor_refresh_rates):
self.refresh_rate = min(sensor_refresh_rates.values())

# Main form
self.window = None
self.window = None

# Widgets
self.basic_stats = None
Expand Down Expand Up @@ -350,7 +370,7 @@ def draw_chart(self,canvas,y,chart_type):
chart_array = self.cpu_array
else:
chart_array = self.memory_array

for i in range(self.CHART_WIDTH):
if i >= 2:
chart_array[i-2] = chart_array[i]
Expand Down Expand Up @@ -399,7 +419,7 @@ def while_waiting(self):

def update(self):
'''
Update the form in background, this used to be called inside the ThreadJob
Update the form in background, this used to be called inside the ThreadJob
and but now is getting called automatically in while_waiting
'''
try:
Expand Down Expand Up @@ -511,7 +531,7 @@ def draw(self):
MIN_ALLOWED_TERMINAL_WIDTH = 104
MIN_ALLOWED_TERMINAL_HEIGHT = 28

# Setting the terminal dimensions by querying the underlying curses library
# Setting the terminal dimensions by querying the underlying curses library
self._logger.info("Detected terminal size to be {0}".format(self.window.curses_pad.getmaxyx()))
global PREVIOUS_TERMINAL_HEIGHT, PREVIOUS_TERMINAL_WIDTH
max_y,max_x = self.window.curses_pad.getmaxyx()
Expand Down Expand Up @@ -595,7 +615,7 @@ def draw(self):
max_height=CPU_USAGE_WIDGET_HEIGHT,
max_width=CPU_USAGE_WIDGET_WIDTH
)
self.cpu_chart.value = ""
self.cpu_chart.value = ""
self.cpu_chart.entry_widget.editable = False


Expand Down Expand Up @@ -626,14 +646,17 @@ def draw(self):
# So (tput lines - rely) should be at least 3
ACTIONS_WIDGET_REL_X = LEFT_OFFSET
ACTIONS_WIDGET_REL_Y = PROCESSES_INFO_WIDGET_REL_Y + PROCESSES_INFO_WIDGET_HEIGHT
self._logger.info("Trying to draw the actions box, x1 {0} y1 {1}".format(ACTIONS_WIDGET_REL_X,
self._logger.info("Trying to draw the actions box, x1 {0} y1 {1}".format(ACTIONS_WIDGET_REL_X,
ACTIONS_WIDGET_REL_Y)
)
self.actions = self.window.add(npyscreen.FixedText,
relx=ACTIONS_WIDGET_REL_X,
rely=ACTIONS_WIDGET_REL_Y
)
self.actions.value = "^K:Kill\t\t^N:Memory Sort\t\t^T:Time Sort\t\t^R:Reset\t\tg:Top\t\t^Q:Quit\t\t^F:Filter\t\t^L:Process Info"
global shortcut_keys
for k, v in shortcut_keys.items():
self.actions.value += k + ":" + v + "\t\t"

self.actions.display()
self.actions.editable = False

Expand Down

0 comments on commit dd90fc1

Please sign in to comment.