diff --git a/.gitignore b/.gitignore
index 3c571fa7..633af797 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ build
.vscode
*~
.goutputstream-*
+subprojects/*/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index e93f9575..01511385 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@
language: node_js
node_js:
- - 10/*
+ - 10.17.0
sudo: required
diff --git a/data/Application.css b/data/Application.css
new file mode 100644
index 00000000..4a728fba
--- /dev/null
+++ b/data/Application.css
@@ -0,0 +1,95 @@
+.roundy-label,
+.roundy-label:hover,
+.roundy-label:selected,
+.roundy-label:selected:focus,
+.roundy-label:hover:selected {
+ background-image: none;
+ background-color: @SILVER_300;
+ box-shadow: none;
+ color: alpha(@text_color, 0.7);
+ font-weight: 700;
+ border-radius: 10px;
+ padding: 0 6px;
+ margin: 0 3px;
+ border-width: 0;
+}
+
+.username-current {
+ background-color: @SILVER_300;
+ color: @SILVER_900;
+}
+
+.username-other {
+ background-color: @BANANA_300;
+ color: #381F00;
+}
+
+.username-root {
+ background-color: @STRAWBERRY_300;
+ color: @SILVER_100;
+}
+
+.state_badge,
+.state_badge:hover,
+.state_badge:selected,
+.state_badge:selected:focus,
+.state_badge:hover:selected {
+ background-image: none;
+ background-color: @BLUEBERRY_500;
+ box-shadow: none;
+ color: white;
+ font-weight: 700;
+ border-radius: 10px;
+ padding: 0 6px;
+ margin: 0 3px;
+ border-width: 0;
+}
+
+.command_wrapper {
+ background-color: #fdf6e3;
+ border-radius: 3px;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.20);
+ padding: 5px;
+}
+
+.command text {
+ background-color: #fdf6e3;
+}
+.command {
+ font-family: monospace;
+}
+
+.pid {
+ color:grey;
+font-weight:bold;
+font-size:9px;
+}
+
+.graph {
+ border: 1px @SILVER_300 solid;
+ border-radius: 6px
+
+}
+
+.open_files_list_box_wrapper {
+ border: 1px @SILVER_300 solid;
+}
+
+.open_files_list_box {
+
+}
+.open_files_list_box_row {
+ border-bottom: 1px @SILVER_100 solid;
+}
+
+.open_files_list_box_row label {
+ font-family: monospace;
+ /* background-color: @SILVER_300; */
+ padding: 6px 2px 2px;
+ border-radius: 4px;
+}
+
+.open_files_list_box_row image {
+ color: @SILVER_700;
+ margin: 6px;
+}
diff --git a/data/com.github.stsdc.monitor.appdata.xml.in b/data/com.github.stsdc.monitor.appdata.xml.in
index 82e4ce5b..2a191d2c 100644
--- a/data/com.github.stsdc.monitor.appdata.xml.in
+++ b/data/com.github.stsdc.monitor.appdata.xml.in
@@ -25,11 +25,13 @@
https://github.com/stsdc/monitor/issues
-
+
- - Bugfix (potential) of crushes and high CPU usage
- - Update German translation (Carsten Dietrich)
+ - Detailed process info in sidebar
+ - CPU frequency in tooltip (Ryo Nakano)
+ - Removed tree view . This fixes high CPU usage, indicator hangs, and app crashes
+ - Special thanks to gavr, Ryo Nakano, Adam Bieńkowski and Daniele Cocca
diff --git a/data/com.github.stsdc.monitor.screenshot.png b/data/com.github.stsdc.monitor.screenshot.png
index 428bd264..92336770 100644
Binary files a/data/com.github.stsdc.monitor.screenshot.png and b/data/com.github.stsdc.monitor.screenshot.png differ
diff --git a/data/css.gresource.xml b/data/css.gresource.xml
new file mode 100644
index 00000000..63daa2e3
--- /dev/null
+++ b/data/css.gresource.xml
@@ -0,0 +1,6 @@
+
+
+
+ Application.css
+
+
\ No newline at end of file
diff --git a/data/icons/file-deleted-symbolic.svg b/data/icons/file-deleted-symbolic.svg
new file mode 100644
index 00000000..44b787bd
--- /dev/null
+++ b/data/icons/file-deleted-symbolic.svg
@@ -0,0 +1,97 @@
+
+
diff --git a/data/icons/icons.indicator.gresource.xml b/data/icons/icons.indicator.gresource.xml
index 981a39a9..8f863b06 100644
--- a/data/icons/icons.indicator.gresource.xml
+++ b/data/icons/icons.indicator.gresource.xml
@@ -4,5 +4,6 @@
cpu-symbolic.svg
ram-symbolic.svg
swap-symbolic.svg
+ file-deleted-symbolic.svg
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index 5bb9e234..1332b260 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+com.github.stsdc.monitor (0.7.0) bionic; urgency=low
+
+ * Detailed process info in sidebar
+ * CPU frequency in tooltip (Ryo Nakano)
+
+-- Stanisław Dac Sat, 11 Apr 2020 21:01:15 +0200
+
com.github.stsdc.monitor (0.6.2) bionic; urgency=low
* Bugfix (potential) of crushes and high CPU usage
diff --git a/meson.build b/meson.build
index ce056adc..6c9f6690 100644
--- a/meson.build
+++ b/meson.build
@@ -1,16 +1,20 @@
-project('com.github.stsdc.monitor', 'vala', 'c', version: '0.6.2')
+project('com.github.stsdc.monitor', 'vala', 'c', version: '0.7.0')
# these are Meson modules
gnome = import('gnome')
i18n = import('i18n')
+
# common dirs
prefix = get_option('prefix')
datadir = join_paths(prefix, get_option('datadir'))
libdir = join_paths(prefix, get_option('libdir'))
icondir = join_paths(datadir, 'icons', 'hicolor')
+livechart_proj = subproject('live-chart')
+livechart_dep = livechart_proj.get_variable('livechart_dep')
+
# and these are project dependencies
glib = dependency('glib-2.0')
granite = dependency('granite', version: '>= 5.2.0')
@@ -18,7 +22,7 @@ gtk = dependency('gtk+-3.0')
gee = dependency('gee-0.8')
gio = dependency('gio-2.0')
gobject = dependency('gobject-2.0')
-bamf = dependency('libbamf3')
+bamf = dependency('libbamf3') # to remove
gtop = dependency('libgtop-2.0')
wnck = dependency('libwnck-3.0')
wingpanel = dependency('wingpanel-2.0')
@@ -31,36 +35,62 @@ configure_file(output: 'config.h', configuration: config_data)
config_h_dir = include_directories('.')
icons_gresource = gnome.compile_resources(
- 'gresource_icons',
- 'data/icons/icons.indicator.gresource.xml',
+ 'gresource_icons', 'data/icons/icons.indicator.gresource.xml',
source_dir: 'data/icons',
- c_name: 'as'
+ c_name: 'as1'
+)
+
+css_gresource = gnome.compile_resources(
+ 'gresource_css', 'data/css.gresource.xml',
+ source_dir: 'data',
+ c_name: 'as2'
)
c_args = [
'-include', 'config.h',
'-DWNCK_I_KNOW_THIS_IS_UNSTABLE',
- '-w'
+ '-w',
+ # '-g',
+ # '--save-temps'
]
executable(
meson.project_name(),
icons_gresource,
+ css_gresource,
'src/Monitor.vala',
'src/MainWindow.vala',
'src/Utils.vala',
- 'src/Widgets/OverallView.vala',
- 'src/Widgets/Search.vala',
- 'src/Widgets/Headerbar.vala',
+ # Views
+ 'src/Views/ProcessView/ProcessView.vala',
+ 'src/Views/ProcessView/ProcessInfoView/ProcessInfoView.vala',
+ 'src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala',
+
+ # Widgets related only to ProcessInfoView
+ 'src/Views/ProcessView/ProcessInfoView/Preventor.vala',
+ 'src/Views/ProcessView/ProcessInfoView/RoundyLabel.vala',
+ 'src/Views/ProcessView/ProcessInfoView/Chart.vala',
+ 'src/Views/ProcessView/ProcessInfoView/ProcessInfoHeader.vala',
+ 'src/Views/ProcessView/ProcessInfoView/ProcessInfoCPURAM.vala',
+ 'src/Views/ProcessView/ProcessInfoView/ProcessInfoIOStats.vala',
+ 'src/Views/ProcessView/ProcessInfoView/OpenFilesListBox.vala',
+
+ # Widgets
+ 'src/Widgets/Headerbar/Headerbar.vala',
+ 'src/Widgets/Headerbar/Search.vala',
'src/Widgets/Statusbar/Statusbar.vala',
+
- 'src/Models/GenericModel.vala',
- 'src/Models/ModelHelper.vala',
+ # Models
+ 'src/Models/TreeViewModel.vala',
+ # Other
'src/Managers/AppManager.vala',
'src/Managers/ProcessManager.vala',
'src/Managers/Process.vala',
+ 'src/Managers/ProcessStructs.vala',
+ 'src/Managers/ProcessUtils.vala',
'src/Services/Shortcuts.vala',
'src/Services/DBusServer.vala',
@@ -78,10 +108,11 @@ executable(
gee,
gio,
gobject,
- bamf,
+ bamf, # to remove
gtop,
wnck,
gdk_x11,
+ livechart_dep,
meson.get_compiler('c').find_library('m', required : false),
meson.get_compiler('vala').find_library('posix')
],
diff --git a/po/es.po b/po/es.po
index 24795026..46610ccb 100644
--- a/po/es.po
+++ b/po/es.po
@@ -43,36 +43,32 @@ msgid "Monitor"
msgstr "Monitor"
#: src/Widgets/Headerbar.vala:23
-#, fuzzy
msgid "End Process"
msgstr "Terminar proceso"
#: src/Widgets/Headerbar.vala:26
-#, fuzzy
msgid "End selected process"
msgstr "Terminar proceso"
#: src/Widgets/Headerbar.vala:30
-#, fuzzy
msgid "Kill Process"
-msgstr ""
+msgstr "Matar proceso"
#: src/Widgets/Headerbar.vala:32
msgid "Kill selected process"
-msgstr ""
+msgstr "Matar los procesos seleccionados"
#: src/Widgets/Headerbar.vala:42
msgid "Settings"
msgstr "Preferencias"
#: src/Widgets/Headerbar.vala:56
-#, fuzzy
msgid "Show an indicator:"
-msgstr "Mostrar icone de panel"
+msgstr "Mostrar un icono en el panel:"
#: src/Widgets/Headerbar.vala:62
msgid "Start in background:"
-msgstr "Arrancar en segundo plano"
+msgstr "Arrancar en segundo plano:"
#. setup name column
#: src/Widgets/OverallView.vala:21
@@ -108,7 +104,6 @@ msgid "Search Process"
msgstr "Buscar proceso"
#: src/Widgets/Search.vala:15
-#, fuzzy
msgid "Type process name or PID to search"
msgstr "Introduce el nombre del proceso o el PID"
diff --git a/src/Indicator/Indicator.vala b/src/Indicator/Indicator.vala
index 2af66860..6fa42614 100644
--- a/src/Indicator/Indicator.vala
+++ b/src/Indicator/Indicator.vala
@@ -7,7 +7,7 @@ public class Monitor.Indicator : Wingpanel.Indicator {
private DBusClient dbusclient;
construct {
- Gtk.IconTheme.get_default().add_resource_path("/com/github/stsdc/monitor/icons");
+ Gtk.IconTheme.get_default ().add_resource_path ("/com/github/stsdc/monitor/icons");
settings = new Settings ("com.github.stsdc.monitor.settings");
this.visible = false;
display_widget = new Widgets.DisplayWidget ();
@@ -18,9 +18,9 @@ public class Monitor.Indicator : Wingpanel.Indicator {
dbusclient.monitor_vanished.connect (() => this.visible = false);
dbusclient.monitor_appeared.connect (() => this.visible = settings.get_boolean ("indicator-state"));
- dbusclient.interface.indicator_state.connect((state) => this.visible = state);
+ dbusclient.interface.indicator_state.connect ((state) => this.visible = state);
- dbusclient.interface.update.connect((sysres) => {
+ dbusclient.interface.update.connect ((sysres) => {
display_widget.cpu_widget.percentage = sysres.cpu_percentage;
display_widget.memory_widget.percentage = sysres.memory_percentage;
});
diff --git a/src/Indicator/Services/DBusClient.vala b/src/Indicator/Services/DBusClient.vala
index dd5f6c7d..116f66cf 100644
--- a/src/Indicator/Services/DBusClient.vala
+++ b/src/Indicator/Services/DBusClient.vala
@@ -6,7 +6,7 @@ public interface Monitor.DBusClientInterface : Object {
public signal void indicator_state (bool state);
}
-public class Monitor.DBusClient : Object{
+public class Monitor.DBusClient : Object {
public DBusClientInterface? interface = null;
private static GLib.Once instance;
diff --git a/src/MainWindow.vala b/src/MainWindow.vala
index 624f6c2b..2ea83eb4 100644
--- a/src/MainWindow.vala
+++ b/src/MainWindow.vala
@@ -5,14 +5,10 @@
// Widgets
public Headerbar headerbar;
// private Gtk.Button process_info_button;
- private Gtk.ScrolledWindow process_view_window;
- public OverallView process_view;
- private Statusbar statusbar;
- public GenericModel generic_model;
- public Gtk.TreeModelSort sort_model;
+ public ProcessView process_view;
- public Gtk.TreeModelFilter filter;
+ private Statusbar statusbar;
public DBusServer dbusserver;
@@ -40,25 +36,21 @@
// TODO: Granite.Widgets.ModeButton to switch between view modes
- // add a process view
- process_view_window = new Gtk.ScrolledWindow (null, null);
- generic_model = new GenericModel ();
- process_view = new OverallView (generic_model);
+ // process_manager = new ProcessManager();
+ process_view = new ProcessView ();
headerbar = new Headerbar (this);
set_titlebar (headerbar);
- process_view_window.add (process_view);
-
statusbar = new Statusbar ();
var main_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
- main_box.pack_start (process_view_window, true, true, 0);
+ main_box.pack_start (process_view, true, true, 0);
main_box.pack_start (statusbar, false, true, 0);
this.add (main_box);
updater = Updater.get_default ();
- dbusserver = DBusServer.get_default();
+ dbusserver = DBusServer.get_default ();
updater.update.connect ((sysres) => {
statusbar.update (sysres);
@@ -66,10 +58,16 @@
dbusserver.indicator_state (MonitorApp.settings.get_boolean ("indicator-state"));
});
+ // updating processes every 2 seconds
+ Timeout.add_seconds (2, () => {
+ process_view.update();
+ return true;
+ });
+
dbusserver.quit.connect (() => app.quit());
dbusserver.show.connect (() => {
- this.deiconify();
- this.present();
+ this.deiconify ();
+ this.present ();
setup_window_state ();
this.show_all ();
});
diff --git a/src/Managers/AppManager.vala b/src/Managers/AppManager.vala
index c4b0d56b..3420c88c 100644
--- a/src/Managers/AppManager.vala
+++ b/src/Managers/AppManager.vala
@@ -7,79 +7,79 @@ namespace Monitor {
public int[] pids;
public uint32 xid;
}
- /**
- * Wrapper for Bamf.Matcher
- */
- public class AppManager {
- public signal void application_opened (App app);
- public signal void application_closed (App app);
+ /**
+ * Wrapper for Bamf.Matcher
+ */
+ public class AppManager {
+ public signal void application_opened (App app);
+ public signal void application_closed (App app);
- static AppManager? app_manager = null;
+ static AppManager? app_manager = null;
private Bamf.Matcher? matcher;
private Gee.ArrayList transient_xids;
- public static AppManager get_default () {
- if (app_manager == null)
- app_manager = new AppManager ();
- return app_manager;
- }
+ public static AppManager get_default () {
+ if (app_manager == null)
+ app_manager = new AppManager ();
+ return app_manager;
+ }
- public AppManager () {
+ public AppManager () {
transient_xids = new Gee.ArrayList ();
- matcher = Bamf.Matcher.get_default ();
+ matcher = Bamf.Matcher.get_default ();
matcher.view_opened.connect_after (handle_view_opened);
- matcher.view_closed.connect_after (handle_view_closed);
- }
+ matcher.view_closed.connect_after (handle_view_closed);
+ }
- ~AppManager () {
- matcher.view_opened.disconnect (handle_view_opened);
- matcher.view_closed.disconnect (handle_view_closed);
- matcher = null;
- }
+ ~AppManager () {
+ matcher.view_opened.disconnect (handle_view_opened);
+ matcher.view_closed.disconnect (handle_view_closed);
+ matcher = null;
+ }
// Function retrieves a Bamf.view, checks if it's a window,
// then extracts name, icon, desktop_file and pid. After that,
// sends signal.
- private void handle_view_opened (Bamf.View view) {
+ private void handle_view_opened (Bamf.View view) {
if (view is Bamf.Window && is_main_window (view)) {
int[] win_pids = {};
var window = (Bamf.Window)view;
var app = matcher.get_application_for_window (window);
- win_pids += (int)window.get_pid();
+ win_pids += (int)window.get_pid ();
if (has_desktop_file (app.get_desktop_file ())) {
- debug ("Handle View Opened: %s", view.get_name());
+ debug ("Handle View Opened: %s", view.get_name ());
application_opened (
App () {
name = app.get_name (),
icon = app.get_icon (),
desktop_file = app.get_desktop_file (),
pids = win_pids,
- xid = app.get_xids ().index(0)
+ xid = app.get_xids ().index (0)
}
);
}
}
- }
+ }
- private void handle_view_closed (Bamf.View view) {
+ private void handle_view_closed (Bamf.View view) {
if (view is Bamf.Window && is_main_window (view)) {
int[] win_pids = {};
var window = (Bamf.Window)view;
var app = matcher.get_application_for_window (window);
- win_pids += (int)window.get_pid();
+ win_pids += (int)window.get_pid ();
if (has_desktop_file (app.get_desktop_file ())) {
- debug ("Handle View Closed: %s", view.get_name());
+ debug ("Handle View Closed: %s", view.get_name ());
application_closed (
App () {
name = app.get_name (),
icon = app.get_icon (),
desktop_file = app.get_desktop_file (),
pids = win_pids,
- xid = app.get_xids ().index(0)
+ xid = app.get_xids ().index (0)
}
);
}
@@ -107,7 +107,7 @@ namespace Monitor {
icon = bamf_app.get_icon (),
desktop_file = bamf_app.get_desktop_file (),
pids = win_pids,
- xid = bamf_app.get_xids ().index(0)
+ xid = bamf_app.get_xids ().index (0)
};
}
}
@@ -116,21 +116,21 @@ namespace Monitor {
private bool is_main_window (Bamf.View view) {
var window_type = ((Bamf.Window)view).get_window_type ();
- debug ("Window type: %d, Is transient: %d", window_type, (int)is_transient(view));
+ debug ("Window type: %d, Is transient: %d", window_type, (int)is_transient (view));
return (window_type == Bamf.WindowType.NORMAL ||
- window_type == Bamf.WindowType.DOCK) && !is_transient(view);
+ window_type == Bamf.WindowType.DOCK) && !is_transient (view);
}
// if window is transient add its xid to array and return true
private bool is_transient (Bamf.View view) {
- if(transient_xids.size > 0 && transient_xids.contains(((Bamf.Window)view).get_xid())) {
+ if (transient_xids.size > 0 && transient_xids.contains (((Bamf.Window)view).get_xid ())) {
return true;
}
if (((Bamf.Window)view).get_transient () != null) {
- transient_xids.add (((Bamf.Window)view).get_xid());
+ transient_xids.add (((Bamf.Window)view).get_xid ());
return true;
}
return false;
}
- }
+ }
}
diff --git a/src/Managers/Process.vala b/src/Managers/Process.vala
index 3a0bb76e..7f0a094e 100644
--- a/src/Managers/Process.vala
+++ b/src/Managers/Process.vala
@@ -1,180 +1,306 @@
+public class Monitor.Process : GLib.Object {
+ // The size of each RSS page, in bytes
+ // private static long PAGESIZE = Posix.sysconf (Posix._SC_PAGESIZE);
+ public signal void fd_permission_error (string error);
-namespace Monitor {
- public class Process {
- // The size of each RSS page, in bytes
- // private static long PAGESIZE = Posix.sysconf (Posix._SC_PAGESIZE);
+ // Whether or not the PID leads to something
+ public bool exists { get; private set; }
- // Whether or not the PID leads to something
- public bool exists { get; private set; }
+ // Full command from cmdline file
+ public string command { get; private set; }
- // Process ID
- public int pid { get; private set; }
+ // If process is an installed app, this will contain its name,
+ // otherwise it is just a trimmed command
+ public string application_name;
- // Parent Process ID
- public int ppid { get; private set; }
+ // User id
+ public int uid;
- // Process Group ID; 0 if a kernal process/thread
- public int pgrp { get; private set; }
+ public string username;
- // Command from stat file, truncated to 16 chars
- public string comm { get; private set; }
+ Icon _icon;
+ public Icon icon {
+ get { return _icon; }
+ set {
+ if (value == null) {
+ _icon = ProcessUtils.get_default_icon ();
+ } else {
+ _icon = value;
+ }
+ }
+ }
+
+ // Contains info about io
+ public ProcessIO io;
+
+ // Contains status info
+ public ProcessStatus stat;
+
+ public ProcessStatusMemory statm;
+
+ public Gee.HashSet open_files_paths;
- // Full command from cmdline file
- public string command { get; private set; }
+ /**
+ * CPU usage of this process from the last time that it was updated, measured in percent
+ *
+ * Will be 0 on first update.
+ */
+ public double cpu_percentage { get; private set; }
- /**
- * CPU usage of this process from the last time that it was updated, measured in percent
- *
- * Will be 0 on first update.
- */
- public double cpu_usage { get; private set; }
+ private uint64 cpu_last_used;
- private uint64 cpu_last_used;
+ //Memory usage of the process, measured in KiB.
- //Memory usage of the process, measured in KiB.
+ public uint64 mem_usage { get; private set; }
+ public double mem_percentage { get; private set; }
- public uint64 mem_usage { get; private set; }
+ private uint64 last_total;
- private uint64 last_total;
+ const int history_buffer_size = 30;
+ public Gee.ArrayList cpu_percentage_history = new Gee.ArrayList();
+ public Gee.ArrayList mem_percentage_history = new Gee.ArrayList();
- // Construct a new process
- public Process (int _pid) {
- pid = _pid;
- last_total = 0;
- exists = read_stat (0, 1) && read_cmdline ();
+ // Construct a new process
+ public Process (int _pid) {
+ _icon = ProcessUtils.get_default_icon ();
+
+ open_files_paths = new Gee.HashSet ();
+
+ last_total = 0;
+
+ io = { };
+ stat = { };
+ stat.pid = _pid;
+
+ // getting uid
+ GTop.ProcUid proc_uid;
+ GTop.get_proc_uid (out proc_uid, stat.pid);
+ uid = proc_uid.uid;
+
+ // getting username
+ unowned Posix.Passwd passwd = Posix.getpwuid (uid);
+ username = passwd.pw_name;
+
+ exists = parse_stat () && read_cmdline ();
+ get_usage (0, 1);
+ }
+
+
+ // Updates the process to get latest information
+ // Returns if the update was successful
+ public bool update (uint64 cpu_total, uint64 cpu_last_total) {
+ exists = parse_stat ();
+ if (exists) {
+ get_usage (cpu_total, cpu_last_total);
+ parse_io ();
+ parse_statm ();
+ get_open_files ();
}
+ return exists;
+ }
- // Updates the process to get latest information
- // Returns if the update was successful
- public bool update (uint64 cpu_total, uint64 cpu_last_total) {
- exists = read_stat (cpu_total, cpu_last_total);
+ // Kills the process
+ // Returns if kill was successful
+ public bool kill () {
+ // Sends a kill signal that cannot be ignored
+ if (Posix.kill (stat.pid, Posix.Signal.KILL) == 0) {
+ return true;
+ }
+ return false;
+ }
- return exists;
+ // Ends the process
+ // Returns if end was successful
+ public bool end () {
+ // Sends a terminate signal
+ if (Posix.kill (stat.pid, Posix.Signal.TERM) == 0) {
+ return true;
}
+ return false;
+ }
- // Kills the process
- // Returns if kill was successful
- public bool kill () {
- // Sends a kill signal that cannot be ignored
- if (Posix.kill (pid, Posix.Signal.KILL) == 0) {
- return true;
- }
+ private bool parse_io () {
+ var io_file = File.new_for_path ("/proc/%d/io".printf (stat.pid));
+
+ if (!io_file.query_exists ()) {
return false;
}
- // Ends the process
- // Returns if end was successful
- public bool end () {
- // Sends a terminate signal
- if (Posix.kill (pid, Posix.Signal.TERM) == 0) {
- return true;
+ try {
+ var dis = new DataInputStream (io_file.read ());
+ string ? line;
+
+ while ((line = dis.read_line ()) != null) {
+ var splitted_line = line.split (":");
+ switch (splitted_line[0]) {
+ case "wchar" :
+ io.wchar = uint64.parse (splitted_line[1]);
+ break;
+ case "rchar" :
+ io.rchar = uint64.parse (splitted_line[1]);
+ break;
+ case "syscr" :
+ io.syscr = uint64.parse (splitted_line[1]);
+ break;
+ case "syscw" :
+ io.syscw = uint64.parse (splitted_line[1]);
+ break;
+ case "read_bytes" :
+ io.read_bytes = uint64.parse (splitted_line[1]);
+ break;
+ case "write_bytes" :
+ io.write_bytes = uint64.parse (splitted_line[1]);
+ break;
+ case "cancelled_write_bytes" :
+ io.cancelled_write_bytes = uint64.parse (splitted_line[1]);
+ break;
+ default :
+ warning ("Unknown value in /proc/%d/io", stat.pid);
+ break;
+ }
+ }
+ } catch (Error e) {
+ if (e.code != 14) {
+ // if the error is not `no access to file`, because regular user
+ // TODO: remove `magic` number
+
+ warning ("Can't read process io: '%s' %d", e.message, e.code);
}
return false;
}
+ return true;
+ }
- // Reads the /proc/%pid%/stat file and updates the process with the information therein.
- private bool read_stat (uint64 cpu_total, uint64 cpu_last_total) {
- /* grab the stat file from /proc/%pid%/stat */
- var stat_file = File.new_for_path ("/proc/%d/stat".printf (pid));
+ // Reads the /proc/%pid%/stat file and updates the process with the information therein.
+ private bool parse_stat () {
+ string ? stat_contents = ProcessUtils.read_file ("/proc/%d/stat".printf (stat.pid));
- /* make sure that it exists, not an error if it doesn't */
- if (!stat_file.query_exists ()) {
- return false;
- }
+ if (stat_contents == null) {
+ return false;
+ }
- try {
- // read the single line from the file
- var dis = new DataInputStream (stat_file.read ());
- string? stat_contents = dis.read_line ();
+ // Split the contents into an array and parse each value that we care about
- if (stat_contents == null) {
- stderr.printf ("Error reading stat file '%s': couldn't read_line ()\n", stat_file.get_path ());
- return false;
- }
+ // But first we have to extract the command name, since it might include spaces
+ // First find the command in stat file. It is inside `(command)`
+
+ Regex regex = /\((.*?)\)/; // <- there should be no spaces; uncrustify adds them
+
+ MatchInfo match_info;
+ regex.match (stat_contents, 0, out match_info);
+ string matched_command = match_info.fetch (0);
- // Get process UID
- GTop.ProcUid uid;
- GTop.get_proc_uid (out uid, pid);
- ppid = uid.ppid; // pid of parent process
- pgrp = uid.pgrp; // process group id
+ // Remove command from stat_contents
+ stat_contents = stat_contents.replace (matched_command, "");
+ // split the string and extract some stats
+ var splitted_stat = stat_contents.split (" ");
+ stat.comm = matched_command[1 : -1];
- // Get CPU usage by process
- GTop.ProcTime proc_time;
- GTop.get_proc_time (out proc_time, pid);
- cpu_usage = ((double)(proc_time.rtime - cpu_last_used)) / (cpu_total - cpu_last_total);
- cpu_last_used = proc_time.rtime;
+ stat.state = splitted_stat[2];
+ stat.ppid = int.parse (splitted_stat[3]);
+ stat.pgrp = int.parse (splitted_stat[4]);
+ stat.priority = int.parse (splitted_stat[17]);
+ stat.nice = int.parse (splitted_stat[18]);
+ stat.num_threads = int.parse (splitted_stat[19]);
- // Get memory usage by process
- GTop.Memory mem;
- GTop.get_mem (out mem);
+ return true;
+ }
+
+ private bool parse_statm () {
+ string ? statm_contents = ProcessUtils.read_file ("/proc/%d/statm".printf (stat.pid));
- GTop.ProcMem proc_mem;
- GTop.get_proc_mem (out proc_mem, pid);
- mem_usage = (proc_mem.resident - proc_mem.share) / 1024; // in KiB
+ if (statm_contents == null) return false;
- if (Gdk.Display.get_default () is Gdk.X11.Display) {
- Wnck.ResourceUsage resu = Wnck.ResourceUsage.pid_read (Gdk.Display.get_default(), pid);
- mem_usage += (resu.total_bytes_estimate / 1024);
+ var splitted_statm = statm_contents.split (" ");
+ statm.size = int.parse (splitted_statm[0]);
+ statm.resident = int.parse (splitted_statm[1]);
+ statm.shared = int.parse (splitted_statm[2]);
+ statm.trs = int.parse (splitted_statm[3]);
+ statm.lrs = int.parse (splitted_statm[4]);
+ statm.drs = int.parse (splitted_statm[5]);
+ statm.dt = int.parse (splitted_statm[6]);
+
+ return true;
+ }
+
+ private bool get_open_files () {
+ try {
+ string directory = "/proc/%d/fd".printf (stat.pid);
+ Dir dir = Dir.open (directory, 0);
+ string ? name = null;
+ while ((name = dir.read_name ()) != null) {
+ string path = Path.build_filename (directory, name);
+
+ if (FileUtils.test (path, FileTest.IS_SYMLINK)) {
+ string real_path = FileUtils.read_link (path);
+ // debug(content);
+ open_files_paths.add (real_path);
}
- } catch (Error e) {
- warning ("Can't read process stat: '%s'", e.message);
- return false;
}
+ } catch (FileError err) {
+ if (err is FileError.ACCES) {
+ fd_permission_error (err.message);
+ } else {
+ warning (err.message);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Reads the /proc/%pid%/cmdline file and updates from the information contained therein.
+ */
+ private bool read_cmdline () {
+ string ? cmdline = ProcessUtils.read_file ("/proc/%d/cmdline".printf (stat.pid));
+ if (cmdline.length <= 0) {
+ // was empty, not an error
return true;
+ }
+
+ command = cmdline;
+ return true;
}
- /**
- * Reads the /proc/%pid%/cmdline file and updates from the information contained therein.
- */
- private bool read_cmdline () {
- // grab the cmdline file from /proc/%pid%/cmdline
- var cmdline_file = File.new_for_path ("/proc/%d/cmdline".printf (pid));
-
- // make sure that it exists
- if (!cmdline_file.query_exists ()) {
- warning ("File '%s' doesn't exist.\n", cmdline_file.get_path ());
- return false;
- }
- try {
- // read the single line from the file
- var dis = new DataInputStream (cmdline_file.read ());
- uint8[] cmdline_contents_array = new uint8[4097]; // 4096 is max size with a null terminator
- var size = dis.read (cmdline_contents_array);
-
- if (size <= 0) {
- // was empty, not an error
- return true;
- }
+ private void get_usage (uint64 cpu_total, uint64 cpu_last_total) {
+ // Get CPU usage by process
+ GTop.ProcTime proc_time;
+ GTop.get_proc_time (out proc_time, stat.pid);
+ cpu_percentage = 100 * ((double)(proc_time.rtime - cpu_last_used)) / (cpu_total - cpu_last_total);
+ cpu_last_used = proc_time.rtime;
- // cmdline is a single line file with each arg seperated by a null character ('\0')
- // convert all \0 and \n to spaces
- for (int pos = 0; pos < size; pos++) {
- if (cmdline_contents_array[pos] == '\0' || cmdline_contents_array[pos] == '\n') {
- cmdline_contents_array[pos] = ' ';
- }
- }
- cmdline_contents_array[size] = '\0';
- string cmdline_contents = (string) cmdline_contents_array;
+ // Making CPU history
+ if (cpu_percentage_history.size == history_buffer_size) {
+ cpu_percentage_history.remove_at (0);
+ }
+ cpu_percentage_history.add (cpu_percentage);
- //TODO: need to make sure that this works
- GTop.ProcState proc_state;
- GTop.get_proc_state (out proc_state, pid);
+ // Get memory usage by process
+ GTop.Memory mem;
+ GTop.get_mem (out mem);
- command = cmdline_contents;
+ GTop.ProcMem proc_mem;
+ GTop.get_proc_mem (out proc_mem, stat.pid);
+ mem_usage = (proc_mem.resident - proc_mem.share) / 1024; // in KiB
- }
- catch (Error e) {
- stderr.printf ("Error reading cmdline file '%s': %s\n", cmdline_file.get_path (), e.message);
- return false;
- }
+ // also if it is using X Window Server
+ if (Gdk.Display.get_default () is Gdk.X11.Display) {
+ Wnck.ResourceUsage resu = Wnck.ResourceUsage.pid_read (Gdk.Display.get_default (), stat.pid);
+ mem_usage += (resu.total_bytes_estimate / 1024);
+ }
- return true;
+ var total_installed_memory = (double)mem.total / 1024;
+ mem_percentage = (mem_usage / total_installed_memory) * 100;
+
+ // Making RAM history
+ if (mem_percentage_history.size == history_buffer_size) {
+ mem_percentage_history.remove_at (0);
}
+ mem_percentage_history.add (mem_percentage);
}
}
diff --git a/src/Managers/ProcessManager.vala b/src/Managers/ProcessManager.vala
index 3743728d..a730f78c 100644
--- a/src/Managers/ProcessManager.vala
+++ b/src/Managers/ProcessManager.vala
@@ -14,8 +14,10 @@ namespace Monitor {
uint64[] cpu_last_useds = new uint64[32];
uint64[] cpu_last_totals = new uint64[32];
- private Gee.HashMap process_list;
+ private Gee.TreeMap process_list;
private Gee.HashSet kernel_process_blacklist;
+ private Gee.HashMap apps_info_list;
+
public signal void process_added (Process process);
public signal void process_removed (int pid);
@@ -23,14 +25,77 @@ namespace Monitor {
// Construct a new ProcessManager
public ProcessManager () {
- process_list = new Gee.HashMap ();
+ process_list = new Gee.TreeMap ();
kernel_process_blacklist = new Gee.HashSet ();
+ apps_info_list = new Gee.HashMap ();
+
+ populate_apps_info ();
+
update_processes.begin ();
- // move timeout outside
- Timeout.add (2000, handle_timeout);
}
+ public void populate_apps_info() {
+
+ var _apps_info = AppInfo.get_all ();
+
+ foreach (AppInfo app_info in _apps_info) {
+
+ string commandline = (app_info.get_commandline ());
+ // debug ("%s\n", commandline);
+
+ // GLib.DesktopAppInfo? dai = info as GLib.DesktopAppInfo;
+
+ // if (dai != null) {
+ // string id = dai.get_string ("X-Flatpak");
+ // if (id != null)
+ // appid_map.insert (id, info);
+ // }
+
+
+ if (commandline == null)
+ continue;
+
+ // sanitize_cmd (ref cmd);
+ apps_info_list.set (commandline, app_info);
+ }
+ }
+
+ // private static void sanitize_cmd(ref string? commandline) {
+ // if (commandline == null)
+ // return;
+
+ // // flatpak: parse the command line of the containerized program
+ // if (commandline.contains("flatpak run")) {
+ // var index = commandline.index_of ("--command=") + 10;
+ // commandline = commandline.substring (index);
+ // }
+
+ // // TODO: unify this with the logic in get_full_process_cmd
+ // // commandline = Process.first_component (commandline);
+ // // commandline = Path.get_basename (commandline);
+ // // commandline = Process.sanitize_name (commandline);
+
+ // // Workaround for google-chrome
+ // if (commandline.contains ("google-chrome-stable"))
+ // commandline = "chrome";
+ // }
+
+ // public static AppInfo? app_info_for_process (Process p) {
+ // AppInfo? info = null;
+
+ // if (p.command != null)
+ // info = apps_info[p.command];
+
+ // if (info == null && p.app_id != null)
+ // info = appid_map[p.app_id];
+
+ // return info;
+ // }
+
+
+
+
/**
* Gets a process by its pid, making sure that it's updated.
*/
@@ -60,8 +125,8 @@ namespace Monitor {
// go through and add all of the processes with PPID set to this one
foreach (var process in process_list.values) {
- if (process.ppid == pid) {
- sub_processes.add (process.pid);
+ if (process.stat.ppid == pid) {
+ sub_processes.add (process.stat.pid);
}
}
@@ -75,18 +140,10 @@ namespace Monitor {
return process_list.read_only_view;
}
- /**
- * Handle updating the process list
- */
- private bool handle_timeout () {
- update_processes.begin ();
- return true;
- }
-
/**
* Gets all new process and adds them
*/
- private async void update_processes () {
+ public async void update_processes () {
/* CPU */
GTop.Cpu cpu_data;
GTop.get_cpu (out cpu_data);
@@ -110,7 +167,7 @@ namespace Monitor {
foreach (var process in process_list.values) {
if (!process.update (cpu_data.total, cpu_last_total)) {
/* process doesn't exist any more, flag it for removal! */
- remove_me.add (process.pid);
+ remove_me.add (process.stat.pid);
}
}
@@ -122,7 +179,7 @@ namespace Monitor {
var uid = Posix.getuid ();
GTop.ProcList proclist;
var pids = GTop.get_proclist (out proclist, GTop.GLIBTOP_KERN_PROC_UID, uid);
- // var pids = GTop.get_proclist (out proclist, GTop.GLIBTOP_KERN_PROC_ALL, 0);
+ // var pids = GTop.get_proclist (out proclist, GTop.GLIBTOP_KERN_PROC_ALLfla, uid);
for (int i = 0; i < proclist.number; i++) {
int pid = pids[i];
@@ -137,7 +194,7 @@ namespace Monitor {
cpu_last_useds = useds;
cpu_last_totals = cpu_data.xcpu_total;
- /* call the updated signal so that subscribers can update */
+ /* emit the updated signal so that subscribers can update */
updated ();
}
@@ -150,8 +207,20 @@ namespace Monitor {
// create the process
var process = new Process (pid);
+ // placeholding shortened commandline
+ process.application_name = ProcessUtils.sanitize_commandline (process.command);
+
+ // checking maybe it's an application
+ foreach (var key in apps_info_list.keys) {
+ if (key.contains (process.application_name)) {
+ process.application_name = apps_info_list.get (key).get_name ();
+ // debug (apps_info_list.get (key).get_icon ().to_string ());
+ process.icon = apps_info_list.get (key).get_icon ();
+ }
+ }
+
if (process.exists) {
- if (process.pgrp != 0) {
+ if (process.stat.pgrp != 0) {
// regular process, add it to our cache
process_list.set (pid, process);
@@ -184,5 +253,7 @@ namespace Monitor {
kernel_process_blacklist.remove (pid);
}
}
+
+
}
}
diff --git a/src/Managers/ProcessStructs.vala b/src/Managers/ProcessStructs.vala
new file mode 100644
index 00000000..335085fb
--- /dev/null
+++ b/src/Managers/ProcessStructs.vala
@@ -0,0 +1,97 @@
+// For more info look at: http://man7.org/linux/man-pages/man5/proc.5.html
+
+public struct Monitor.ProcessIO {
+
+ // characters read
+ public uint64 rchar;
+
+ // characters written
+ public uint64 wchar;
+
+ // read syscalls
+ public uint64 syscr;
+
+ // write syscalls
+ public uint64 syscw;
+
+ // Attempt to count the number of bytes which this process
+ // really did cause to be fetched from the storage layer
+ public uint64 read_bytes;
+
+ // Attempt to count the number of bytes which this process
+ // caused to be sent to the storage layer.
+ public uint64 write_bytes;
+
+ public uint64 cancelled_write_bytes;
+}
+
+public struct Monitor.ProcessStatusMemory {
+
+ // total program size (pages) (same as VmSize in status)
+ public uint64 size;
+
+ // size of memory portions (pages) (same as VmRSS in status)
+ public uint64 resident;
+
+ // number of pages that are shared
+ // (i.e. backed by a file, same as RssFile+RssShmem in status)
+ public uint64 shared;
+
+ // number of pages that are 'code' (not including libs; broken,
+ // includes data segment)
+ public uint64 trs;
+
+ // number of pages of library (always 0 on 2.6)
+ public uint64 lrs;
+
+ // number of pages of data/stack (including libs; broken,
+ // includes library text)
+ public uint64 drs;
+
+ // number of dirty pages (always 0 on 2.6)
+ public uint64 dt;
+}
+
+public struct Monitor.ProcessStatus {
+ // process ID
+ public int pid;
+
+ // The filename of the executable, in parentheses.
+ // This is visible whether or not the executable is
+ // swapped out.
+ public string comm;
+
+ public string state;
+
+ // The PID of the parent of this process.
+ public int ppid;
+
+ // The process group ID of the process.
+ public int pgrp;
+
+ // The session ID of the process.
+ public uint session;
+
+ // The controlling terminal of the process.
+ // (The minor device number is contained in
+ // the combination of bits 31 to 20 and 7 to 0;
+ // the major device number is in bits 15 to 8.)
+ public uint tty_nr;
+
+ // The ID of the foreground process group of the con‐
+ // trolling terminal of the process.
+ public uint tpgid;
+
+ // The nice value, a value in the
+ // range 19 (low priority) to -20 (high priority).
+ public int nice;
+
+ public int priority;
+
+ // Number of threads in this process
+ public int num_threads;
+
+ // The time the process started after system boot.
+ public uint64 starttime;
+
+}
\ No newline at end of file
diff --git a/src/Managers/ProcessUtils.vala b/src/Managers/ProcessUtils.vala
new file mode 100644
index 00000000..f766461f
--- /dev/null
+++ b/src/Managers/ProcessUtils.vala
@@ -0,0 +1,61 @@
+public class Monitor.ProcessUtils {
+
+ // checks if it is run by shell
+ private static bool is_shell (string chunk) {
+ if ("sh" == chunk || "bash" == chunk || "zsh" == chunk) {
+ debug (chunk);
+ return true;
+ }
+ return false;
+ }
+
+ public static string sanitize_commandline (string commandline) {
+ // splitting command; might include many options
+ var splitted_commandline = commandline.split (" ");
+
+ // check if started by any shell
+ if (is_shell (splitted_commandline[0])) {
+ return Path.get_basename (splitted_commandline[1]);
+ }
+
+ return Path.get_basename (splitted_commandline[0]);
+ }
+
+ public static string? read_file (string path) {
+ var file = File.new_for_path (path);
+
+ /* make sure that it exists, not an error if it doesn't */
+ if (!file.query_exists ()) {
+ return null;
+ }
+ var text = new StringBuilder ();
+ try {
+ var dis = new DataInputStream (file.read ());
+
+ // Doing this because of cmdline file.
+ // cmdline is a single line file with each arg seperated by a null character ('\0')
+ string line = dis.read_upto ("\0", 1, null);
+ while (line != null) {
+ text.append (line);
+ text.append (" ");
+ dis.skip (1);
+ line = dis.read_upto ("\0", 1, null);
+ }
+
+ return text.str;
+ } catch (Error e) {
+ warning ("Error reading cmdline file '%s': %s\n", file.get_path (), e.message);
+ return null;
+ }
+ }
+
+ public static Icon? get_default_icon () {
+ try {
+ return Icon.new_for_string ("application-x-executable");
+ } catch (Error e) {
+ warning (e.message);
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Models/GenericModel.vala b/src/Models/GenericModel.vala
deleted file mode 100644
index 65a40297..00000000
--- a/src/Models/GenericModel.vala
+++ /dev/null
@@ -1,316 +0,0 @@
-namespace Monitor {
-
- public class GenericModel : Gtk.TreeStore {
- ModelHelper helper;
- private AppManager app_manager;
- private ProcessManager process_manager;
- private Gee.Map app_rows;
- private Gee.Map process_rows;
- private Gtk.TreeIter background_apps_iter;
- public Gtk.TreeStore model { get; private set; }
- private Type[] types;
- construct {
- app_rows = new Gee.HashMap ();
- process_rows = new Gee.HashMap ();
-
- types = new Type[] {
- typeof (string),
- typeof (string),
- typeof (double),
- typeof (int64),
- typeof (int),
- };
- set_column_types(types);
-
- helper = new ModelHelper(this);
-
- process_manager = ProcessManager.get_default ();
- process_manager.process_added.connect ((process) => add_process (process));
- process_manager.process_removed.connect ((pid) => remove_process (pid));
- process_manager.updated.connect (update_model);
-
- app_manager = AppManager.get_default ();
- app_manager.application_opened.connect ((app) => { add_app (app); });
- app_manager.application_closed.connect ((app) => { remove_app (app); });
-
- Idle.add (() => { add_running_apps (); return false; } );
- Idle.add (() => { add_running_processes (); return false; } );
-
- add_background_apps_row ();
- }
-
- public GenericModel () { }
-
- private void add_running_apps () {
- debug ("add_running_applications");
- // get all running applications and add them to the tree store
- var running_applications = app_manager.get_running_applications ();
- foreach (var app in running_applications) {
- add_app (app);
- }
- }
-
- private void add_running_processes () {
- debug ("add_running_processes");
- var running_processes = process_manager.get_process_list ();
- foreach (var process in running_processes.values) {
- add_process (process);
- }
- }
-
- private void update_model () {
- foreach (int pid in process_rows.keys) {
- update_process (pid);
- }
-
- foreach (var desktop_file in app_rows.keys) {
- update_app (desktop_file);
- }
- update_app_row (background_apps_iter);
- }
-
- private void update_process (int pid) {
- var process = process_manager.get_process (pid);
-
- if (process_rows.has_key (pid) && process != null) {
- Gtk.TreeIter process_iter = process_rows[pid].iter;
- helper.set_dynamic_columns (process_iter, process.cpu_usage, process.mem_usage);
- }
- }
-
- private void update_app (string desktop_file) {
- if (!app_rows.has_key (desktop_file))
- return;
-
- var app_iter = app_rows[desktop_file].iter;
- update_app_row (app_iter);
- }
-
- private void update_app_row (Gtk.TreeIter iter) {
- int64 total_mem = 0;
- double total_cpu = 0;
- get_children_total (iter, ref total_mem, ref total_cpu);
- helper.set_dynamic_columns (iter, total_cpu, total_mem);
- }
-
- private void get_children_total (Gtk.TreeIter iter, ref int64 memory, ref double cpu) {
- // go through all children and add up CPU/Memory usage
- // TODO: this is a naive way to do things
- Gtk.TreeIter child_iter;
-
- if (iter_children (out child_iter, iter)) {
- do {
- get_children_total (child_iter, ref memory, ref cpu);
- Value cpu_value;
- Value memory_value;
- get_value (child_iter, Column.CPU, out cpu_value);
- get_value (child_iter, Column.MEMORY, out memory_value);
- memory += memory_value.get_int64 ();
- cpu += cpu_value.get_double ();
- } while (iter_next (ref child_iter));
- }
- }
-
- private void add_app (App app) {
- if (app_rows.has_key (app.desktop_file)) {
- // App already in application rows, no need to add
- debug ("Skip App");
- return;
- }
- // add the application to the model
- Gtk.TreeIter iter;
- append (out iter, null);
- helper.set_static_columns (iter, app.icon, app.name, app.pids[0]);
-
- // add the application to our cache of app_rows
- var row = new ApplicationProcessRow (iter);
- app_rows.set (app.desktop_file, row);
-
- // go through the windows of the application and add all of the pids
- foreach (var pid in app.pids) {
- debug ("Add App: %s %d", app.name, pid);
- add_process_to_row (iter, pid);
- }
- update_app (app.desktop_file);
- }
-
- private bool remove_app (App app) {
- debug ("Remove App: %s", app.name);
-
- // check if desktop file is in our row cache
- if (!app_rows.has_key (app.desktop_file)) {
- return false;
- }
- var app_iter = app_rows[app.desktop_file].iter;
-
- // reparent children to background processes; let the ProcessManager take care of removing them
- Gtk.TreeIter child_iter;
- while (iter_children (out child_iter, app_iter)) {
- Value pid_value;
- get_value (child_iter, Column.PID, out pid_value);
- debug ("Reparent Process to Background: %d", pid_value.get_int ());
- add_process_to_row (background_apps_iter, pid_value.get_int ());
- }
-
- // remove row from model
- remove (ref app_iter);
-
- // remove row from row cache
- app_rows.unset (app.desktop_file);
-
- return true;
- }
-
- // THE BUG IS SOMEWHERE IN HERE
- // reparent children to background processes
- private void reparent (ref Gtk.TreeIter iter) {
- Gtk.TreeIter child_iter;
- Value pid_value_prev;
-
- while (iter_children (out child_iter, iter)) {
- Value pid_value;
- get_value (child_iter, Column.PID, out pid_value);
- pid_value_prev = pid_value;
- debug( "reparent %d", pid_value.get_int ());
-
- add_process_to_row (background_apps_iter, pid_value.get_int ());
- }
- }
-
- private void remove_process (int pid) {
- debug ("remove process %d from model".printf(pid));
- // if process rows has pid
- if (process_rows.has_key (pid)) {
- var row = process_rows.get (pid);
- Gtk.TreeIter iter = row.iter;
-
- debug ("remove process: user_data %d, stamp %d", (int) iter.user_data, iter.stamp);
-
- Value pid_value;
- get_value (iter, Column.PID, out pid_value);
-
- // Column.NAME, for example returns (null)
- // Column.PID return 0
-
- debug("removing %d", pid_value.get_int());
-
- // sometimes iter has null values
- // this potentially should prevent segfaults
- if (pid_value.get_int() != 0) {
- reparent (ref iter);
- // remove row from model
- remove (ref iter);
- }
-
- // remove row from row cache
- process_rows.unset (pid);
- }
- }
-
- private bool add_process (Process process) {
- debug ("add_process %d to model", process.pid);
- if (process_rows.has_key (process.pid)) {
- // process already in process rows, no need to add
- debug ("Skipping Add Process %d", process.pid);
- return false;
- }
-
- // var process = process_manager.get_process (pid);
-
- if (process != null && process.pid != 1) {
- debug ("Parent PID: %d", process.ppid);
- if (process.ppid > 1) {
- // is a sub process of something
- if (process_rows.has_key (process.ppid)) {
- // is a subprocess of something in the rows
- add_process_to_row (process_rows[process.ppid].iter, process.pid);
- } else {
- add_process_to_row (background_apps_iter, process.pid);
- debug ("Is a subprocess of something but has no parent");
- }
- // if parent not in yet, then child will be added in after
- } else {
- // isn't a subprocess of anything, put it into background processes
- // it can be moved afterwards to an application
- add_process_to_row (background_apps_iter, process.pid);
- }
-
- return true;
- }
-
- return false;
- }
-
- // Addes a process to an existing row;
- // reparenting it and it's children if it already exists.
- private bool add_process_to_row (Gtk.TreeIter row, int pid) {
- var process = process_manager.get_process (pid);
- debug ("add_process_to_row pid:%d", pid);
-
- if (process != null) {
- // if process is already in list, then we need to reparent it and it's children
- // can't remove it now because we need to remove all of the children first.
- Gtk.TreeIter? old_location = null;
- if (process_rows.has_key (pid)) {
- old_location = process_rows[pid].iter;
- }
-
- // add the process to the model
- Gtk.TreeIter iter;
- append (out iter, row);
-
- helper.set_static_columns (iter, "application-x-executable", process.command, process.pid);
-
- helper.set_dynamic_columns (iter, process.cpu_usage, process.mem_usage);
-
- // add the process to our cache of process_rows
- var process_row = new ApplicationProcessRow (iter);
- process_rows.set (pid, process_row);
-
- // add all subprocesses to this row, recursively
- var sub_processes = process_manager.get_sub_processes (pid);
- foreach (var sub_pid in sub_processes) {
- // only add subprocesses that either arn't in yet or are parented to the old location
- // i.e. skip if subprocess is already in but isn't an ancestor of this process row
- if (process_rows.has_key (sub_pid) && (
- (old_location != null && !is_ancestor (old_location, process_rows[sub_pid].iter))
- || old_location == null)) {
- continue;
- }
- add_process_to_row (iter, sub_pid);
- }
-
- // remove old row where the process used to be
- if (old_location != null) {
- remove (ref old_location);
- }
-
- return true;
- }
-
- return false;
- }
-
- private void add_background_apps_row () {
- append (out background_apps_iter, null);
- helper.set_static_columns (background_apps_iter, "system-run", _("Background Applications"));
- }
-
- public void kill_process (int pid) {
- if (pid > 0) {
- var process = process_manager.get_process (pid);
- process.kill ();
- info ("Kill:%d",process.pid);
- }
- }
-
- public void end_process (int pid) {
- if (pid > 0) {
- var process = process_manager.get_process (pid);
- process.end ();
- info ("End:%d",process.pid);
- }
- }
- }
-
-}
diff --git a/src/Models/ModelHelper.vala b/src/Models/ModelHelper.vala
deleted file mode 100644
index 6c1d700c..00000000
--- a/src/Models/ModelHelper.vala
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace Monitor {
-
- public enum Column {
- ICON,
- NAME,
- CPU,
- MEMORY,
- PID,
- }
-
- // can't use TreeIter in HashMap for some reason, wrap it in a class
- public class ApplicationProcessRow {
- public Gtk.TreeIter iter;
- public ApplicationProcessRow (Gtk.TreeIter iter) { this.iter = iter; }
- }
-
- public class ModelHelper {
- private Gtk.TreeStore model;
-
- public ModelHelper (Gtk.TreeStore model) { this.model = model; }
-
- public void set_static_columns (Gtk.TreeIter iter, string icon, string name, int pid=0) {
- model.set (iter,
- Column.NAME, name,
- Column.ICON, icon,
- Column.PID, pid,
- -1);
- }
-
- public void set_dynamic_columns (Gtk.TreeIter iter, double cpu, uint64 mem) {
- model.set (iter,
- Column.CPU, cpu,
- Column.MEMORY, mem,
- -1);
- }
- }
-}
diff --git a/src/Models/TreeViewModel.vala b/src/Models/TreeViewModel.vala
new file mode 100644
index 00000000..665211b4
--- /dev/null
+++ b/src/Models/TreeViewModel.vala
@@ -0,0 +1,102 @@
+public enum Monitor.Column {
+ ICON,
+ NAME,
+ CPU,
+ MEMORY,
+ PID,
+}
+
+public class Monitor.TreeViewModel : Gtk.TreeStore {
+ public ProcessManager process_manager;
+ private Gee.Map process_rows;
+
+ construct {
+ process_rows = new Gee.HashMap ();
+
+ set_column_types (new Type[] {
+ typeof (string),
+ typeof (string),
+ typeof (double),
+ typeof (int64),
+ typeof (int),
+ });
+
+ process_manager = ProcessManager.get_default ();
+ process_manager.process_added.connect ((process) => add_process (process));
+ process_manager.process_removed.connect ((pid) => remove_process (pid));
+ process_manager.updated.connect (update_model);
+
+ Idle.add (() => { add_running_processes (); return false; });
+
+
+ }
+
+ private void add_running_processes () {
+ debug ("add_running_processes");
+ var running_processes = process_manager.get_process_list ();
+ foreach (var process in running_processes.values) {
+ add_process (process);
+ }
+ }
+
+ private bool add_process (Process process) {
+ if (process != null && !process_rows.has_key (process.stat.pid)) {
+ debug ("Add process %d Parent PID: %d", process.stat.pid, process.stat.ppid);
+ // add the process to the model
+ Gtk.TreeIter iter;
+ append (out iter, null); // null means top-level
+
+ // donno what is going on, but maybe just use a string insteead of Icon ??
+ // coz it lagz
+ // string icon_name = process.icon.to_string ();
+
+ set (iter,
+ Column.NAME, process.application_name,
+ Column.ICON, process.icon.to_string (),
+ Column.PID, process.stat.pid,
+ -1);
+
+ // add the process to our cache of process_rows
+ process_rows.set (process.stat.pid, iter);
+ return true;
+ }
+ return false;
+ }
+
+ private void update_model () {
+ foreach (int pid in process_rows.keys) {
+ Process process = process_manager.get_process (pid);
+ Gtk.TreeIter iter = process_rows[pid];
+ set (iter,
+ Column.CPU, process.cpu_percentage,
+ Column.MEMORY, process.mem_usage,
+ -1);
+ }
+ }
+
+ private void remove_process (int pid) {
+ debug ("remove process %d from model".printf (pid));
+ // if process rows has pid
+ if (process_rows.has_key (pid)) {
+ var cached_iter = process_rows.get (pid);
+ remove (ref cached_iter);
+ process_rows.unset (pid);
+ }
+ }
+
+ public void kill_process (int pid) {
+ if (pid > 0) {
+ var process = process_manager.get_process (pid);
+ process.kill ();
+ info ("Kill:%d",process.stat.pid);
+ }
+ }
+
+ public void end_process (int pid) {
+ if (pid > 0) {
+ var process = process_manager.get_process (pid);
+ process.end ();
+ info ("End:%d",process.stat.pid);
+ }
+ }
+}
diff --git a/src/Monitor.vala b/src/Monitor.vala
index 64f72390..330c53ad 100644
--- a/src/Monitor.vala
+++ b/src/Monitor.vala
@@ -7,7 +7,7 @@ namespace Monitor {
private static bool start_in_background = false;
private static bool status_background = false;
- private const GLib.OptionEntry[] cmd_options = {
+ private const GLib.OptionEntry[] CMD_OPTIONS = {
// --start-in-background
{ "start-in-background", 'b', 0, OptionArg.NONE, ref start_in_background, "Start in background with wingpanel indicator", null },
// list terminator
@@ -49,7 +49,7 @@ namespace Monitor {
window.show_all ();
}
- window.process_view.focus_on_first_row ();
+ window.process_view.process_tree_view.focus_on_first_row ();
var quit_action = new SimpleAction ("quit", null);
add_action (quit_action);
@@ -59,6 +59,10 @@ namespace Monitor {
window.destroy ();
}
});
+
+ var provider = new Gtk.CssProvider ();
+ provider.load_from_resource ("/com/github/stsdc/monitor/Application.css");
+ Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
}
public static int main (string [] args) {
@@ -66,7 +70,7 @@ namespace Monitor {
try {
var opt_context = new OptionContext ("");
opt_context.set_help_enabled (true);
- opt_context.add_main_entries (cmd_options, null);
+ opt_context.add_main_entries (CMD_OPTIONS, null);
opt_context.parse (ref args);
} catch (OptionError e) {
print ("Error: %s\n", e.message);
diff --git a/src/Resources/CPU.vala b/src/Resources/CPU.vala
index f65bfb5b..a3b37805 100644
--- a/src/Resources/CPU.vala
+++ b/src/Resources/CPU.vala
@@ -1,36 +1,71 @@
- public class Monitor.CPU : Object {
- private float last_used;
- private float last_total;
- private float load;
+public class Monitor.CPU : Object {
+ private float last_used;
+ private float last_total;
+ private float load;
- GTop.Cpu? cpu;
+ GTop.Cpu ? cpu;
- public int percentage {
- get {
- update ();
- return (int) (Math.round(load * 100));
- }
+ public int percentage {
+ get {
+ update_percentage ();
+ return (int)(Math.round (load * 100));
}
- construct {
- last_used = 0;
- last_total = 0;
+ }
+
+ private double _frequency;
+ public double frequency {
+ get {
+ update_frequency ();
+ // Convert kH to GHz
+ return (double)(_frequency / 1000000);
}
+ }
+
+ construct {
+ last_used = 0;
+ last_total = 0;
+ }
+
+ public CPU () {
+ }
+
+ private void update_percentage () {
+ GTop.get_cpu (out cpu);
- public CPU () { }
+ var used = (float)(cpu.user + cpu.sys + cpu.nice + cpu.irq + cpu.softirq);
+ var idle = (float)(cpu.idle + cpu.iowait);
+ var total = used + idle;
- private void update () {
- GTop.get_cpu (out cpu);
+ var diff_used = used - last_used;
+ var diff_total = total - last_total;
- var used = (float) (cpu.user + cpu.sys + cpu.nice + cpu.irq + cpu.softirq);
- var idle = (float) (cpu.idle + cpu.iowait);
- var total = used + idle;
+ load = diff_used.abs () / diff_total.abs ();
- var diff_used = used - last_used;
- var diff_total = total - last_total;
+ last_used = used;
+ last_total = total;
+ }
- load = diff_used.abs () / diff_total.abs ();
+ // From https://github.com/PlugaruT/wingpanel-monitor/blob/edcfea6a31f794aa44da6d8b997378ea1a8d8fa3/src/Services/Cpu.vala#L61-L85
+ private void update_frequency () {
+ double maxcur = 0;
+ for (uint cpu_id = 0, isize = (int)get_num_processors (); cpu_id < isize; ++cpu_id) {
+ string cur_content;
+ try {
+ FileUtils.get_contents ("/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq".printf (cpu_id), out cur_content);
+ } catch (Error e) {
+ warning (e.message);
+ cur_content = "0";
+ }
- last_used = used;
- last_total = total;
+ var cur = double.parse (cur_content);
+
+ if (cpu_id == 0) {
+ maxcur = cur;
+ } else {
+ maxcur = double.max (cur, maxcur);
+ }
}
+
+ _frequency = (double)maxcur;
}
+}
diff --git a/src/Resources/Core.vala b/src/Resources/Core.vala
index bd23100c..bed3b5b0 100644
--- a/src/Resources/Core.vala
+++ b/src/Resources/Core.vala
@@ -1,6 +1,6 @@
namespace Monitor {
//from Monilet
- public class Core : GLib.Object {
+ public class Core : GLib.Object {
private float last_total;
private float last_used;
@@ -11,23 +11,21 @@ namespace Monitor {
get { update_percentage_used (); return _percentage_used; }
}
- public Core (int number){
- Object (number : number);
+ public Core (int number) {
+ Object (number: number);
last_used = 0;
last_total = 0;
}
- private void update_percentage_used (){
+ private void update_percentage_used () {
GTop.Cpu cpu;
GTop.get_cpu (out cpu);
- var used = cpu.xcpu_user[number] +
- cpu.xcpu_nice[number] +
- cpu.xcpu_sys[number];
+ var used = cpu.xcpu_user[number] + cpu.xcpu_nice[number] + cpu.xcpu_sys[number];
- var difference_used = (float) used - last_used;
- var difference_total = (float) cpu.xcpu_total[number] - last_total;
- var pre_percentage = difference_used.abs () / difference_total.abs (); // calculate the pre percentage
+ var difference_used = (float) used - last_used;
+ var difference_total = (float) cpu.xcpu_total[number] - last_total;
+ var pre_percentage = difference_used.abs () / difference_total.abs (); // calculate the pre percentage
_percentage_used = pre_percentage * 100;
diff --git a/src/Resources/Memory.vala b/src/Resources/Memory.vala
index 0ba335b0..b5a63a01 100644
--- a/src/Resources/Memory.vala
+++ b/src/Resources/Memory.vala
@@ -9,7 +9,7 @@ namespace Monitor {
public int percentage {
get {
update ();
- return (int) (Math.round((used / total) * 100));
+ return (int) (Math.round ((used / total) * 100));
}
}
@@ -22,7 +22,7 @@ namespace Monitor {
private void update () {
GTop.get_mem (out mem);
- total = (double) (mem.total / 1024 / 1024) / 1000;
+ total = (double) (mem.total / 1024 / 1024) / 1000;
used = (double) (mem.user / 1024 / 1024) / 1000;
}
}
diff --git a/src/Services/Shortcuts.vala b/src/Services/Shortcuts.vala
index ec7323d9..9474dac6 100644
--- a/src/Services/Shortcuts.vala
+++ b/src/Services/Shortcuts.vala
@@ -10,23 +10,23 @@ namespace Monitor {
handled = false;
char typed = e.str[0];
- if (typed.isalnum () && !window.headerbar.search.is_focus ) {
+ if (typed.isalnum () && !window.headerbar.search.is_focus) {
window.headerbar.search.activate_entry (e.str);
handled = true;
}
- if((e.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
+ if ((e.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
switch (e.keyval) {
case Gdk.Key.f:
window.headerbar.search.activate_entry ();
handled = true;
break;
case Gdk.Key.e:
- window.process_view.end_process ();
+ window.process_view.process_tree_view.end_process ();
handled = true;
break;
case Gdk.Key.k:
- window.process_view.kill_process ();
+ window.process_view.process_tree_view.kill_process ();
handled = true;
break;
case Gdk.Key.comma:
@@ -42,15 +42,15 @@ namespace Monitor {
switch (e.keyval) {
case Gdk.Key.Return:
- window.process_view.focus_on_first_row ();
+ window.process_view.process_tree_view.focus_on_first_row ();
handled = true;
break;
case Gdk.Key.Left:
- window.process_view.collapse ();
+ window.process_view.process_tree_view.collapse ();
handled = true;
break;
case Gdk.Key.Right:
- window.process_view.expanded ();
+ window.process_view.process_tree_view.expanded ();
handled = true;
break;
default:
diff --git a/src/Services/Updater.vala b/src/Services/Updater.vala
index 01fcde90..9825644c 100644
--- a/src/Services/Updater.vala
+++ b/src/Services/Updater.vala
@@ -26,6 +26,7 @@ namespace Monitor {
private bool update_resources () {
sysres = Utils.SystemResources () {
cpu_percentage = cpu.percentage,
+ cpu_frequency = cpu.frequency,
memory_percentage = memory.percentage,
memory_used = memory.used,
memory_total = memory.total,
diff --git a/src/Utils.vala b/src/Utils.vala
index dbf1939b..40df65e5 100644
--- a/src/Utils.vala
+++ b/src/Utils.vala
@@ -1,6 +1,7 @@
namespace Monitor.Utils {
public struct SystemResources {
public int cpu_percentage;
+ public double cpu_frequency;
public int memory_percentage;
public double memory_used;
public double memory_total;
@@ -8,4 +9,53 @@ namespace Monitor.Utils {
public double swap_used;
public double swap_total;
}
+
+ const string NOT_AVAILABLE = (_("N/A"));
+ const string NO_DATA = "\u2014";
}
+
+ /**
+ * Static helper class for unit formatting
+ * Author: Laurent Callarec @lcallarec
+ */
+ public class Monitor.Utils.HumanUnitFormatter {
+
+ const string[] SIZE_UNITS = {"B", "KiB", "MiB", "GiB", "TiB"};
+ const double KFACTOR = 1024;
+
+ /**
+ * format a string of bytes to an human readable format with units
+ */
+ public static string string_bytes_to_human(string bytes) {
+ double current_size = double.parse(bytes);
+ string current_size_formatted = bytes.to_string() + HumanUnitFormatter.SIZE_UNITS[0];
+
+ for (int i = 0; i<= HumanUnitFormatter.SIZE_UNITS.length; i++) {
+ if (current_size < HumanUnitFormatter.KFACTOR) {
+ return GLib.Math.round(current_size).to_string() + HumanUnitFormatter.SIZE_UNITS[i];
+ }
+ current_size = current_size / HumanUnitFormatter.KFACTOR;
+ }
+
+ return current_size_formatted;
+ }
+
+ public static string int_bytes_to_human(int bytes) {
+ double bytes_double = (double)bytes;
+ string units = _ ("B");
+
+ // convert to MiB if needed
+ if (bytes_double > 1024.0) {
+ bytes_double /= 1024.0;
+ units = _ ("KiB");
+ }
+
+ // convert to GiB if needed
+ if (bytes_double > 1024.0) {
+ bytes_double /= 1024.0;
+ units = _ ("MiB");
+ }
+
+ return "%.1f %s".printf (bytes_double, units);
+ }
+ }
diff --git a/src/Views/ProcessView/ProcessInfoView/Chart.vala b/src/Views/ProcessView/ProcessInfoView/Chart.vala
new file mode 100644
index 00000000..5e025988
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/Chart.vala
@@ -0,0 +1,68 @@
+public class Monitor.Chart : Gtk.Box {
+ private LiveChart.Serie serie;
+ private LiveChart.SmoothLineArea renderer;
+ private LiveChart.Chart chart;
+ private LiveChart.Config config;
+
+
+ construct {
+ vexpand = true;
+ height_request = 60;
+
+ config = new LiveChart.Config ();
+ config.y_axis.unit = "%";
+ config.y_axis.tick_interval = 25;
+ config.y_axis.fixed_max = 100.0;
+ config.y_axis.labels.visible = false;
+ config.x_axis.labels.visible = false;
+
+ config.padding = LiveChart.Padding () {
+ smart = LiveChart.AutoPadding.NONE,
+ top = 0,
+ right = 0,
+ bottom = 0,
+ left = 0
+ };
+
+ chart = new LiveChart.Chart (config);
+ chart.expand = true;
+ chart.legend.visible = false;
+ chart.grid.visible = false;
+ chart.background.main_color = Gdk.RGBA () {
+ red= 1, green= 1, blue= 1, alpha= 1
+ }; //White background
+
+ renderer = new LiveChart.SmoothLineArea (new LiveChart.Values(30));
+
+ serie = new LiveChart.Serie ("CPU 1 usage", renderer);
+ serie.set_main_color ({ 0.35, 0.8, 0.1, 1.0});
+
+ chart.add_serie (serie);
+
+ add (chart);
+ }
+
+ public void update (double value) {
+ chart.add_value (serie, value);
+ }
+
+ public void set_data (Gee.ArrayList history) {
+ var refresh_rate_is_ms = 2000; //your own refresh rate in milliseconds
+ var now = GLib.get_real_time() / 1000; //now in milliseconds
+
+ //the timestamp of the first point
+ //we are considering that it is "now", but because of your data structure
+ //it can be between now and now - 2secs), we can't guess
+ var ts = now - (history.size * refresh_rate_is_ms);
+
+ history.foreach((value) => {
+ renderer.get_values ().add({ts, value});
+ ts += refresh_rate_is_ms;
+ return true;
+ });
+ }
+
+ public void clear () {
+ renderer.get_values ().clear ();
+ }
+}
diff --git a/src/Views/ProcessView/ProcessInfoView/OpenFilesListBox.vala b/src/Views/ProcessView/ProcessInfoView/OpenFilesListBox.vala
new file mode 100644
index 00000000..03b17bfd
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/OpenFilesListBox.vala
@@ -0,0 +1,69 @@
+public class Monitor.OpenFilesListBox : Gtk.ScrolledWindow {
+ Gtk.ListBox listbox;
+ construct {
+ get_style_context ().add_class ("open_files_list_box_wrapper");
+ hadjustment = null;
+ vadjustment = null;
+
+ listbox = new Gtk.ListBox ();
+ listbox.get_style_context ().add_class ("open_files_list_box");
+ listbox.set_selection_mode (Gtk.SelectionMode.NONE);
+ listbox.get_style_context ().add_class ("open_files_list_box");
+ listbox.vexpand = true;
+
+ add (listbox);
+ }
+
+ public void update (Process process) {
+ // removeing all "rows"
+ // probably should be done with model
+ foreach (Gtk.Widget element in listbox.get_children ())
+ listbox.remove (element);
+
+
+ if (process.open_files_paths.size > 0) {
+ foreach (var path in process.open_files_paths) {
+ // display only real paths
+ // probably should be done in process class
+ if (path.substring(0, 1) == "/") {
+ var row = new OpenFilesListBoxRow (path, path.contains("(deleted)"));
+ listbox.add (row);
+ }
+ }
+ }
+ show_all ();
+ }
+}
+
+
+public class Monitor.OpenFilesListBoxRow : Gtk.ListBoxRow {
+ construct {
+
+ get_style_context ().add_class ("open_files_list_box_row");
+ }
+ public OpenFilesListBoxRow (string _text, bool is_deleted) {
+ var text = _text;
+ var grid = new Gtk.Grid ();
+ grid.column_spacing = 2;
+
+ var icon = new Gtk.Image ();
+ icon.halign = Gtk.Align.START;
+ icon.pixel_size = 16;
+ icon.gicon = new ThemedIcon ("emblem-documents-symbolic");
+
+ if (is_deleted) {
+ icon = new Gtk.Image.from_icon_name ("file-deleted-symbolic", Gtk.IconSize.SMALL_TOOLBAR);
+ icon.tooltip_text = _("Deleted");
+ text = text.replace ("(deleted)", "");
+ }
+
+
+ grid.attach (icon, 0, 0, 1, 1);
+
+ Gtk.Label label = new Gtk.Label (text);
+ label.halign = Gtk.Align.START;
+ grid.attach (label, 1, 0, 1, 1);
+
+ add (grid);
+ }
+}
\ No newline at end of file
diff --git a/src/Views/ProcessView/ProcessInfoView/Preventor.vala b/src/Views/ProcessView/ProcessInfoView/Preventor.vala
new file mode 100644
index 00000000..61d5ffed
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/Preventor.vala
@@ -0,0 +1,59 @@
+public class Monitor.Preventor : Gtk.Stack {
+
+ private Gtk.Box preventive_action_bar;
+ private Gtk.Label confirmation_label;
+ private Gtk.Button confirm_button;
+ private Gtk.Button deny_button;
+
+ private Gtk.Widget child;
+
+ public signal void confirmed (bool is_confirmed);
+
+ construct {
+ vexpand = true;
+ valign = Gtk.Align.END;
+
+ preventive_action_bar = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ preventive_action_bar.valign = Gtk.Align.START;
+ preventive_action_bar.halign = Gtk.Align.END;
+
+
+ confirmation_label = new Gtk.Label (_ ("Are You sure You want to do this?"));
+ confirmation_label.margin_end = 10;
+
+ confirm_button = new Gtk.Button.with_label (_("Yes"));
+ confirm_button.margin_end = 10;
+ confirm_button.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
+
+ deny_button = new Gtk.Button.with_label (_("No"));
+
+ preventive_action_bar.add (confirmation_label);
+ preventive_action_bar.add (confirm_button);
+ preventive_action_bar.add (deny_button);
+ }
+
+ public Preventor (Gtk.Widget _child, string name) {
+ child = _child;
+ add_named (child, name);
+ add_named (preventive_action_bar, "preventive_action_bar");
+
+ deny_button.clicked.connect (() => {
+ set_transition_type(Gtk.StackTransitionType.SLIDE_UP);
+ set_visible_child (child);
+ confirmed(false);
+ });
+
+ confirm_button.clicked.connect(() => {
+ set_transition_type(Gtk.StackTransitionType.SLIDE_UP);
+ set_visible_child (child);
+ confirmed(true);
+ });
+ }
+
+ public void set_prevention (string confirmation_text) {
+ set_transition_type(Gtk.StackTransitionType.SLIDE_DOWN);
+ confirmation_label.set_text (_(confirmation_text));
+ set_visible_child (preventive_action_bar);
+ }
+
+}
\ No newline at end of file
diff --git a/src/Views/ProcessView/ProcessInfoView/ProcessInfoCPURAM.vala b/src/Views/ProcessView/ProcessInfoView/ProcessInfoCPURAM.vala
new file mode 100644
index 00000000..2e46157a
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/ProcessInfoCPURAM.vala
@@ -0,0 +1,62 @@
+public class Monitor.ProcessInfoCPURAM : Gtk.Grid {
+ private Gtk.Label cpu_label;
+ private Gtk.Label ram_label;
+
+ private Chart cpu_chart;
+ private Chart ram_chart;
+
+ construct {
+ column_spacing = 6;
+ row_spacing = 6;
+ vexpand = false;
+ column_homogeneous = true;
+ row_homogeneous = false;
+
+ cpu_chart = new Chart ();
+ ram_chart = new Chart ();
+
+
+ var cpu_graph_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ cpu_graph_box.get_style_context ().add_class ("graph");
+ cpu_graph_box.add (cpu_chart);
+
+
+
+ var mem_graph_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ mem_graph_box.get_style_context ().add_class ("graph");
+ mem_graph_box.add (ram_chart);
+
+ cpu_label = new Gtk.Label ("CPU: " + Utils.NO_DATA);
+ cpu_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
+ cpu_label.halign = Gtk.Align.START;
+
+ ram_label = new Gtk.Label ("RAM: " + Utils.NO_DATA);
+ ram_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
+ ram_label.halign = Gtk.Align.START;
+
+ attach (cpu_label, 0, 0, 1, 1);
+ attach (ram_label, 1, 0, 1, 1);
+
+ attach (cpu_graph_box, 0, 1, 1, 1);
+ attach (mem_graph_box, 1, 1, 1, 1);
+ }
+
+ public void set_charts_data (Process process) {
+ cpu_chart.set_data (process.cpu_percentage_history);
+ ram_chart.set_data (process.mem_percentage_history);
+ }
+
+ public void update (Process process) {
+ cpu_label.set_text (("CPU: %.1f%%").printf (process.cpu_percentage));
+ ram_label.set_text (("RAM: %.1f%%").printf (process.mem_percentage));
+
+ cpu_chart.update(process.cpu_percentage);
+ ram_chart.update(process.mem_percentage);
+
+ }
+
+ public void clear_graphs () {
+ cpu_chart.clear ();
+ ram_chart.clear ();
+ }
+}
\ No newline at end of file
diff --git a/src/Views/ProcessView/ProcessInfoView/ProcessInfoHeader.vala b/src/Views/ProcessView/ProcessInfoView/ProcessInfoHeader.vala
new file mode 100644
index 00000000..3ec0055d
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/ProcessInfoHeader.vala
@@ -0,0 +1,107 @@
+public class Monitor.ProcessInfoHeader : Gtk.Grid {
+ private Gtk.Image icon;
+ public Gtk.Label state;
+ public Gtk.Label application_name;
+ public RoundyLabel pid;
+ public RoundyLabel ppid;
+ public RoundyLabel pgrp;
+ public RoundyLabel nice;
+ public RoundyLabel priority;
+ public RoundyLabel num_threads;
+ public RoundyLabel username;
+
+ private Regex ? regex;
+
+ construct {
+ column_spacing = 12;
+
+ regex = /(?i:^.*\.(xpm|png)$)/;
+
+ icon = new Gtk.Image.from_icon_name ("application-x-executable", Gtk.IconSize.DIALOG);
+ icon.set_pixel_size (64);
+ icon.valign = Gtk.Align.END;
+
+ state = new Gtk.Label ("?");
+ state.halign = Gtk.Align.START;
+ state.get_style_context ().add_class ("state_badge");
+
+ var icon_container = new Gtk.Fixed ();
+ icon_container.put (icon, 0, 0);
+ icon_container.put (state, -5, 48);
+
+ application_name = new Gtk.Label (_ ("N/A"));
+ application_name.get_style_context ().add_class ("h2");
+ application_name.ellipsize = Pango.EllipsizeMode.END;
+ application_name.tooltip_text = _ ("N/A");
+ application_name.halign = Gtk.Align.START;
+ application_name.valign = Gtk.Align.START;
+
+ pid = new RoundyLabel (_ ("PID"));
+ nice = new RoundyLabel (_ ("NI"));
+ priority = new RoundyLabel (_ ("PRI"));
+ num_threads = new RoundyLabel (_ ("THR"));
+ // ppid = new RoundyLabel (_("PPID"));
+ // pgrp = new RoundyLabel (_("PGRP"));
+
+ // TODO: tooltip_text UID
+ username = new RoundyLabel ("");
+
+ var wrapper = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ wrapper.add (pid);
+ wrapper.add (priority);
+ wrapper.add (nice);
+ wrapper.add (num_threads);
+ wrapper.add (username);
+
+ attach (icon_container, 0, 0, 1, 2);
+ attach (application_name, 1, 0, 3, 1);
+ attach (wrapper, 1, 1, 1, 1);
+
+ }
+
+ public void update (Process process) {
+ application_name.set_text (process.application_name);
+ application_name.tooltip_text = process.command;
+ pid.set_text (process.stat.pid.to_string());
+ nice.set_text (process.stat.nice.to_string());
+ priority.set_text (process.stat.priority.to_string());
+
+ if (process.uid == 0) {
+ username.val.get_style_context ().add_class ("username-root");
+ username.val.get_style_context ().remove_class ("username-other");
+ username.val.get_style_context ().remove_class ("username-current");
+
+
+ } else if (process.uid == (int)Posix.getuid ()) {
+ username.val.get_style_context ().add_class ("username-current");
+ username.val.get_style_context ().remove_class ("username-other");
+ username.val.get_style_context ().remove_class ("username-root");
+ } else {
+ username.val.get_style_context ().add_class ("username-other");
+ username.val.get_style_context ().remove_class ("username-root");
+ username.val.get_style_context ().remove_class ("username-current");
+ }
+
+ username.set_text (process.username);
+ num_threads.set_text (process.stat.num_threads.to_string());
+ state.set_text (process.stat.state);
+ num_threads.set_text (process.stat.num_threads.to_string());
+ set_icon (process);
+ }
+
+ private void set_icon (Process process) {
+ // this construction should be somewhere else
+ var icon_name = process.icon.to_string ();
+
+ if (!regex.match (icon_name)) {
+ icon.set_from_icon_name (icon_name, Gtk.IconSize.DIALOG);
+ } else {
+ try {
+ var pixbuf = new Gdk.Pixbuf.from_file_at_size (icon_name, 64, -1);
+ icon.set_from_pixbuf (pixbuf);
+ } catch (Error e) {
+ warning (e.message);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Views/ProcessView/ProcessInfoView/ProcessInfoIOStats.vala b/src/Views/ProcessView/ProcessInfoView/ProcessInfoIOStats.vala
new file mode 100644
index 00000000..fb5905ba
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/ProcessInfoIOStats.vala
@@ -0,0 +1,88 @@
+public class Monitor.ProcessInfoIOStats : Gtk.Grid {
+ private Gtk.Label rchar_label;
+ private Gtk.Label wchar_label;
+ private Gtk.Label syscr_label;
+ private Gtk.Label syscw_label;
+ private Gtk.Label write_bytes_label;
+ private Gtk.Label read_bytes_label;
+ private Gtk.Label cancelled_write_bytes_label;
+
+ private OpenFilesListBox open_files_listbox;
+
+ construct {
+ column_spacing = 6;
+ row_spacing = 6;
+ column_homogeneous = true;
+ row_homogeneous = false;
+
+ var opened_files_label = create_label (_("Opened files"));
+ opened_files_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
+ opened_files_label.margin_top = 24;
+
+ var characters_label = create_label (_("Characters"));
+ characters_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
+ rchar_label = create_label (_("N/A"));
+ wchar_label = create_label (_("N/A"));
+
+ var system_calls_label = create_label (_("System calls"));
+ system_calls_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
+ syscr_label = create_label (_("N/A"));
+ syscw_label = create_label (_("N/A"));
+
+ var io_label = create_label (_("Read/Written"));
+ io_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
+ write_bytes_label = create_label (_("N/A"));
+ read_bytes_label = create_label (_("N/A"));
+
+ var cancelled_write_label = create_label (_("Cancelled write"));
+ cancelled_write_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
+
+ cancelled_write_bytes_label = create_label (Utils.NO_DATA);
+
+ attach (io_label, 0, 1, 1, 1);
+ attach (create_label_with_icon(read_bytes_label, "go-up-symbolic"), 0, 2, 1, 1);
+ attach (create_label_with_icon(write_bytes_label, "go-down-symbolic"), 0, 3, 1, 1);
+
+ attach (cancelled_write_label, 1, 1, 1, 1);
+ attach (cancelled_write_bytes_label, 1, 2, 1, 1);
+
+ attach (opened_files_label, 0, 3, 3, 1);
+
+ open_files_listbox = new OpenFilesListBox ();
+ attach (open_files_listbox, 0, 4, 3, 1);
+ }
+
+ public ProcessInfoIOStats() {
+
+ }
+
+ public void update (Process process) {
+ write_bytes_label.set_text (Utils.HumanUnitFormatter.int_bytes_to_human((int)process.io.write_bytes));
+ read_bytes_label.set_text (Utils.HumanUnitFormatter.int_bytes_to_human((int)process.io.read_bytes));
+ cancelled_write_bytes_label.set_text (Utils.HumanUnitFormatter.int_bytes_to_human((int)process.io.cancelled_write_bytes));
+
+ open_files_listbox.update (process);
+ }
+
+ private Gtk.Label create_label (string text) {
+ var label = new Gtk.Label (text);
+ label.halign = Gtk.Align.START;
+ return label;
+ }
+
+ private Gtk.Image create_icon (string icon_name) {
+ var icon = new Gtk.Image ();
+ icon.gicon = new ThemedIcon (icon_name);
+ icon.halign = Gtk.Align.START;
+ icon.pixel_size = 16;
+ return icon;
+ }
+
+ private Gtk.Grid create_label_with_icon (Gtk.Label label, string icon_name) {
+ var grid = new Gtk.Grid ();
+ grid.column_spacing = 2;
+ grid.attach (create_icon (icon_name), 0, 0, 1, 1);
+ grid.attach (label, 1, 0, 1, 1);
+ return grid;
+ }
+}
\ No newline at end of file
diff --git a/src/Views/ProcessView/ProcessInfoView/ProcessInfoView.vala b/src/Views/ProcessView/ProcessInfoView/ProcessInfoView.vala
new file mode 100644
index 00000000..26929c1f
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/ProcessInfoView.vala
@@ -0,0 +1,131 @@
+public class Monitor.ProcessInfoView : Gtk.Box {
+ private Process _process;
+ public Process ? process {
+ get { return _process; }
+ set {
+
+ // remember to disconnect before assigning a new value
+ if (_process != null) {
+ _process.fd_permission_error.disconnect (show_permission_error_infobar);
+ }
+ _process = value;
+
+ process_info_header.update (_process);
+ process_info_io_stats.update (_process);
+
+ process_info_cpu_ram.clear_graphs ();
+ process_info_cpu_ram.set_charts_data (_process);
+
+ permission_error_infobar.revealed = false;
+ _process.fd_permission_error.connect (show_permission_error_infobar);
+
+ }
+ }
+ public string ? icon_name;
+ private Gtk.ScrolledWindow command_wrapper;
+
+ private Gtk.InfoBar permission_error_infobar;
+ private Gtk.Label permission_error_label;
+
+ private ProcessInfoHeader process_info_header;
+ private ProcessInfoIOStats process_info_io_stats;
+ private ProcessInfoCPURAM process_info_cpu_ram;
+
+
+ private Regex ? regex;
+ private Gtk.Grid grid;
+
+ private Gtk.Button end_process_button;
+ private Gtk.Button kill_process_button;
+
+ private Preventor preventor;
+
+ public ProcessInfoView () {
+ orientation = Gtk.Orientation.VERTICAL;
+ hexpand = true;
+
+ permission_error_infobar = new Gtk.InfoBar ();
+ permission_error_infobar.message_type = Gtk.MessageType.ERROR;
+ permission_error_infobar.revealed = false;
+ permission_error_label = new Gtk.Label (Utils.NO_DATA);
+ permission_error_infobar.get_content_area ().add (permission_error_label);
+ add (permission_error_infobar);
+
+ var grid = new Gtk.Grid ();
+ grid.margin = 12;
+ grid.hexpand = true;
+ grid.column_spacing = 12;
+ add (grid);
+
+
+ process_info_header = new ProcessInfoHeader();
+ grid.attach (process_info_header, 0, 0, 1, 1);
+
+ var sep = new Gtk.Separator(Gtk.Orientation.HORIZONTAL);
+ sep.margin = 12;
+ grid.attach (sep, 0, 1, 1, 1);
+
+ process_info_cpu_ram = new ProcessInfoCPURAM ();
+ grid.attach (process_info_cpu_ram, 0, 2, 1, 1);
+
+ process_info_io_stats = new ProcessInfoIOStats ();
+ grid.attach (process_info_io_stats, 0, 4, 1, 1);
+
+
+ var process_action_bar = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ process_action_bar.valign = Gtk.Align.END;
+ process_action_bar.halign = Gtk.Align.END;
+
+ end_process_button = new Gtk.Button.with_label (_("End Process"));
+ end_process_button.margin_end = 10;
+ end_process_button.tooltip_markup = Granite.markup_accel_tooltip ({"E"}, _("End selected process"));
+ var end_process_button_context = end_process_button.get_style_context ();
+ end_process_button_context.add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
+
+ kill_process_button = new Gtk.Button.with_label (_("Kill Process"));
+ kill_process_button.tooltip_markup = Granite.markup_accel_tooltip ({"K"}, _("Kill selected process"));
+ var kill_process_button_context = kill_process_button.get_style_context ();
+ kill_process_button_context.add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
+
+ process_action_bar.add (end_process_button);
+ process_action_bar.add (kill_process_button);
+
+ Preventor preventor = new Preventor (process_action_bar, "process_action_bar");
+
+ kill_process_button.clicked.connect(() => {
+ preventor.set_prevention (_("Confirm kill of the process?"));
+ preventor.confirmed.connect((is_confirmed) => {
+ if (is_confirmed) process.kill(); // maybe add a toast that process killed
+ });
+ });
+
+ end_process_button.clicked.connect(() => {
+ preventor.set_prevention (_("Confirm end of the process?"));
+ preventor.confirmed.connect((is_confirmed) => {
+ if (is_confirmed) process.end(); // maybe add a toast that process ended
+ });
+ });
+
+ grid.attach (preventor, 0, 5, 1, 1);
+
+
+
+ }
+
+ private void show_permission_error_infobar (string error) {
+ if (permission_error_infobar.revealed == false) {
+ permission_error_label.set_text (error);
+ permission_error_infobar.revealed = true;
+ }
+ }
+
+ public void update () {
+ if (process != null) {
+ process_info_header.update (process);
+ process_info_cpu_ram.update (process);
+ process_info_io_stats.update (process);
+
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Views/ProcessView/ProcessInfoView/RoundyLabel.vala b/src/Views/ProcessView/ProcessInfoView/RoundyLabel.vala
new file mode 100644
index 00000000..7a8964d4
--- /dev/null
+++ b/src/Views/ProcessView/ProcessInfoView/RoundyLabel.vala
@@ -0,0 +1,20 @@
+public class Monitor.RoundyLabel : Gtk.Fixed {
+
+ public Gtk.Label val;
+ public Gtk.Label desc;
+
+ public RoundyLabel (string description) {
+ val = new Gtk.Label (Utils.NO_DATA);
+ val.get_style_context ().add_class ("roundy-label");
+
+ desc = new Gtk.Label (description);
+ desc.get_style_context ().add_class ("pid");
+
+ put(val, 0, 12);
+ put(desc, 6, 0);
+ }
+
+ public void set_text (string text) {
+ val.set_text (text);
+ }
+}
\ No newline at end of file
diff --git a/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala b/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala
new file mode 100644
index 00000000..28351928
--- /dev/null
+++ b/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala
@@ -0,0 +1,205 @@
+public class Monitor.CPUProcessTreeView : Gtk.TreeView {
+ private new TreeViewModel model;
+ private Gtk.TreeViewColumn name_column;
+ private Gtk.TreeViewColumn pid_column;
+ private Gtk.TreeViewColumn cpu_column;
+ private Gtk.TreeViewColumn memory_column;
+ private Regex ? regex;
+
+ public signal void process_selected (Process process);
+
+ public CPUProcessTreeView (TreeViewModel model) {
+ this.model = model;
+ regex = /(?i:^.*\.(xpm|png)$)/;
+
+ // setup name column
+ name_column = new Gtk.TreeViewColumn ();
+ name_column.title = _ ("Process Name");
+ name_column.expand = true;
+ name_column.min_width = 250;
+ name_column.set_sort_column_id (Column.NAME);
+
+ var icon_cell = new Gtk.CellRendererPixbuf ();
+ name_column.pack_start (icon_cell, false);
+ // name_column.add_attribute (icon_cell, "icon_name", Column.ICON);
+ name_column.set_cell_data_func (icon_cell, icon_cell_layout);
+
+ var name_cell = new Gtk.CellRendererText ();
+ name_cell.ellipsize = Pango.EllipsizeMode.END;
+ name_cell.set_fixed_height_from_font (1);
+ name_column.pack_start (name_cell, false);
+ name_column.add_attribute (name_cell, "text", Column.NAME);
+ insert_column (name_column, -1);
+
+ // setup cpu column
+ var cpu_cell = new Gtk.CellRendererText ();
+ cpu_cell.xalign = 0.5f;
+
+ cpu_column = new Gtk.TreeViewColumn.with_attributes (_ ("CPU"), cpu_cell);
+ cpu_column.expand = false;
+ cpu_column.set_cell_data_func (cpu_cell, cpu_usage_cell_layout);
+ cpu_column.alignment = 0.5f;
+ cpu_column.set_sort_column_id (Column.CPU);
+ insert_column (cpu_column, -1);
+
+ // setup memory column
+ var memory_cell = new Gtk.CellRendererText ();
+ memory_cell.xalign = 0.5f;
+
+ memory_column = new Gtk.TreeViewColumn.with_attributes (_ ("Memory"), memory_cell);
+ memory_column.expand = false;
+ memory_column.set_cell_data_func (memory_cell, memory_usage_cell_layout);
+ memory_column.alignment = 0.5f;
+ memory_column.set_sort_column_id (Column.MEMORY);
+ insert_column (memory_column, -1);
+
+ // setup PID column
+ var pid_cell = new Gtk.CellRendererText ();
+ pid_cell.xalign = 0.5f;
+ pid_column = new Gtk.TreeViewColumn.with_attributes (_ ("PID"), pid_cell);
+ pid_column.set_cell_data_func (pid_cell, pid_cell_layout);
+ pid_column.expand = false;
+ pid_column.alignment = 0.5f;
+ pid_column.set_sort_column_id (Column.PID);
+ pid_column.add_attribute (pid_cell, "text", Column.PID);
+ insert_column (pid_column, -1);
+
+ // resize all of the columns
+ columns_autosize ();
+
+ set_model (model);
+
+
+ cursor_changed.connect (_cursor_changed);
+ // model.process_manager.updated.connect (_cursor_changed);
+ }
+ public void icon_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer icon_cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
+ Value icon_name;
+ model.get_value (iter, Column.ICON, out icon_name);
+ string path = ((string)icon_name);
+
+ if (regex.match (path)) {
+
+ try {
+ Gdk.Pixbuf icon = new Gdk.Pixbuf.from_file_at_size (path, 16, -1);
+ (icon_cell as Gtk.CellRendererPixbuf).pixbuf = icon;
+ } catch (Error e) {
+ warning (e.message);
+ }
+ } else {
+ (icon_cell as Gtk.CellRendererPixbuf).icon_name = path;
+ }
+ }
+
+ public void cpu_usage_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
+ // grab the value that was store in the model and convert it down to a usable format
+ Value cpu_usage_value;
+ model.get_value (iter, Column.CPU, out cpu_usage_value);
+ double cpu_usage = cpu_usage_value.get_double ();
+
+ // format the double into a string
+ if (cpu_usage < 0.0)
+ (cell as Gtk.CellRendererText).text = Utils.NO_DATA;
+ else
+ (cell as Gtk.CellRendererText).text = "%.0f%%".printf (cpu_usage);
+ }
+
+ public void memory_usage_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
+ // grab the value that was store in the model and convert it down to a usable format
+ Value memory_usage_value;
+ model.get_value (iter, Column.MEMORY, out memory_usage_value);
+ int64 memory_usage = memory_usage_value.get_int64 ();
+ double memory_usage_double = (double)memory_usage;
+ string units = _ ("KiB");
+
+ // convert to MiB if needed
+ if (memory_usage_double > 1024.0) {
+ memory_usage_double /= 1024.0;
+ units = _ ("MiB");
+ }
+
+ // convert to GiB if needed
+ if (memory_usage_double > 1024.0) {
+ memory_usage_double /= 1024.0;
+ units = _ ("GiB");
+ }
+
+ // format the double into a string
+ if (memory_usage == 0)
+ (cell as Gtk.CellRendererText).text = Utils.NO_DATA;
+ else
+ (cell as Gtk.CellRendererText).text = "%.1f %s".printf (memory_usage_double, units);
+ }
+
+ private void pid_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
+ Value pid_value;
+ model.get_value (iter, Column.PID, out pid_value);
+ int pid = pid_value.get_int ();
+ // format the double into a string
+ if (pid == 0) {
+ (cell as Gtk.CellRendererText).text = Utils.NO_DATA;
+ }
+ }
+
+ public void focus_on_first_row () {
+ Gtk.TreePath tree_path = new Gtk.TreePath.from_indices (0);
+ this.set_cursor (tree_path, null, false);
+ grab_focus ();
+ }
+
+ public void focus_on_child_row () {
+ Gtk.TreePath tree_path = new Gtk.TreePath.from_indices (0, 0);
+ this.set_cursor (tree_path, null, false);
+ grab_focus ();
+ }
+
+ public int get_pid_of_selected () {
+ Gtk.TreeIter iter;
+ Gtk.TreeModel model;
+ int pid = 0;
+ var selection = this.get_selection ().get_selected_rows (out model).nth_data (0);
+ model.get_iter (out iter, selection);
+ model.get (iter, Column.PID, out pid);
+ return pid;
+ }
+
+ // How about GtkTreeSelection ?
+
+ public void expanded () {
+ Gtk.TreeModel model;
+ var selection = this.get_selection ().get_selected_rows (out model).nth_data (0);
+ this.expand_row (selection, false);
+ }
+
+ public void collapse () {
+ Gtk.TreeModel model;
+ var selection = this.get_selection ().get_selected_rows (out model).nth_data (0);
+ this.collapse_row (selection);
+ }
+
+ public void kill_process () {
+ int pid = get_pid_of_selected ();
+ model.kill_process (pid);
+ }
+
+ public void end_process () {
+ int pid = get_pid_of_selected ();
+ model.end_process (pid);
+ }
+
+ // when row is selected send signal to update process_info_view
+ public void _cursor_changed () {
+ Gtk.TreeModel tree_model;
+ Gtk.TreeIter iter;
+ int pid = 0;
+ var selection = get_selection ().get_selected_rows (out tree_model).nth_data (0);
+
+ if (selection != null) {
+ tree_model.get_iter (out iter, selection);
+ tree_model.get (iter, Column.PID, out pid);
+ Process process = model.process_manager.get_process (pid);
+ process_selected (process);
+ debug ("cursor changed");
+ }
+ }
+}
diff --git a/src/Views/ProcessView/ProcessView.vala b/src/Views/ProcessView/ProcessView.vala
new file mode 100644
index 00000000..8e37bb92
--- /dev/null
+++ b/src/Views/ProcessView/ProcessView.vala
@@ -0,0 +1,45 @@
+public class Monitor.ProcessView : Gtk.Box {
+ public TreeViewModel treeview_model;
+ public CPUProcessTreeView process_tree_view;
+
+ public ProcessInfoView process_info_view;
+
+ construct {
+
+ process_info_view = new ProcessInfoView ();
+
+ // hide on startup
+ process_info_view.no_show_all = true;
+ }
+
+ public ProcessView () {
+ treeview_model = new TreeViewModel ();
+
+ process_tree_view = new CPUProcessTreeView (treeview_model);
+ process_tree_view.process_selected.connect ((process) => on_process_selected (process));
+
+ // making tree view scrollable
+ var process_tree_view_scrolled = new Gtk.ScrolledWindow (null, null);
+ process_tree_view_scrolled.add (process_tree_view);
+
+ var paned = new Gtk.Paned (Gtk.Orientation.HORIZONTAL);
+ paned.pack1 (process_tree_view_scrolled, true, false);
+ paned.pack2 (process_info_view, false, false);
+ // paned.set_min_position (200);
+ paned.set_position (paned.max_position);
+ paned.set_hexpand (true);
+
+ add (paned);
+ }
+
+ public void on_process_selected (Process process) {
+ process_info_view.process = process;
+ process_info_view.no_show_all = false;
+ process_info_view.show_all ();
+ }
+
+ public void update () {
+ process_info_view.update ();
+ treeview_model.process_manager.update_processes.begin();
+ }
+}
\ No newline at end of file
diff --git a/src/Widgets/Headerbar.vala b/src/Widgets/Headerbar/Headerbar.vala
similarity index 66%
rename from src/Widgets/Headerbar.vala
rename to src/Widgets/Headerbar/Headerbar.vala
index 9204bffa..5befb993 100644
--- a/src/Widgets/Headerbar.vala
+++ b/src/Widgets/Headerbar/Headerbar.vala
@@ -2,8 +2,6 @@ namespace Monitor {
public class Headerbar : Gtk.HeaderBar {
private MainWindow window;
- private Gtk.Button end_process_button;
- private Gtk.Button kill_process_button;
private Gtk.Switch show_indicator_switch;
private Gtk.Switch background_switch;
@@ -17,25 +15,6 @@ namespace Monitor {
public Headerbar (MainWindow window) {
this.window = window;
- var button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
- button_box.valign = Gtk.Align.CENTER;
-
- end_process_button = new Gtk.Button.with_label (_("End Process"));
- end_process_button.margin_end = 10;
- end_process_button.clicked.connect (window.process_view.end_process);
- end_process_button.tooltip_markup = Granite.markup_accel_tooltip ({"E"}, _("End selected process"));
- var end_process_button_context = end_process_button.get_style_context ();
- end_process_button_context.add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
-
- kill_process_button = new Gtk.Button.with_label (_("Kill Process"));
- kill_process_button.clicked.connect (window.process_view.kill_process);
- kill_process_button.tooltip_markup = Granite.markup_accel_tooltip ({"K"}, _("Kill selected process"));
- var kill_process_button_context = kill_process_button.get_style_context ();
- kill_process_button_context.add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
-
- button_box.pack_start (end_process_button);
- button_box.pack_end (kill_process_button);
- pack_start (button_box);
var preferences_button = new Gtk.MenuButton ();
preferences_button.has_tooltip = true;
@@ -75,7 +54,7 @@ namespace Monitor {
search = new Search (window);
search.valign = Gtk.Align.CENTER;
- pack_end (search);
+ pack_start (search);
show_indicator_switch.notify["active"].connect (() => {
MonitorApp.settings.set_boolean ("indicator-state", show_indicator_switch.state);
@@ -95,10 +74,5 @@ namespace Monitor {
background_switch.state = false;
}
}
-
- public void set_header_buttons_sensitivity (bool sensitivity) {
- end_process_button.sensitive = sensitivity;
- kill_process_button.sensitive = sensitivity;
- }
}
}
diff --git a/src/Widgets/Search.vala b/src/Widgets/Headerbar/Search.vala
similarity index 80%
rename from src/Widgets/Search.vala
rename to src/Widgets/Headerbar/Search.vala
index cf54fcb8..f698e338 100644
--- a/src/Widgets/Search.vala
+++ b/src/Widgets/Headerbar/Search.vala
@@ -3,40 +3,37 @@ namespace Monitor {
public class Search : Gtk.SearchEntry {
public MainWindow window { get; construct; }
private Gtk.TreeModelFilter filter_model;
- private OverallView process_view;
+ private CPUProcessTreeView process_tree_view;
public Search (MainWindow window) {
Object (window: window);
}
construct {
- this.process_view = window.process_view;
+ this.process_tree_view = window.process_view.process_tree_view;
this.placeholder_text = _("Search Process");
this.tooltip_markup = Granite.markup_accel_tooltip ({"F"}, _("Type process name or PID to search"));
- filter_model = new Gtk.TreeModelFilter (window.generic_model, null);
+ filter_model = new Gtk.TreeModelFilter (window.process_view.treeview_model, null);
connect_signal ();
filter_model.set_visible_func(filter_func);
- process_view.set_model (filter_model);
+ // process_tree_view.set_model (filter_model);
var sort_model = new Gtk.TreeModelSort.with_model (filter_model);
- process_view.set_model (sort_model);
+ process_tree_view.set_model (sort_model);
}
private void connect_signal () {
this.search_changed.connect (() => {
// collapse tree only when search is focused and changed
if (this.is_focus) {
- process_view.collapse_all ();
+ process_tree_view.collapse_all ();
}
filter_model.refilter ();
- // if there's no search result, make "Kill/End Process" buttons in headerbar insensitive to avoid the app crashes
- window.headerbar.set_header_buttons_sensitivity (filter_model.iter_n_children (null) != 0);
-
// focus on child row to avoid the app crashes by clicking "Kill/End Process" buttons in headerbar
- process_view.focus_on_child_row ();
+ process_tree_view.focus_on_child_row ();
this.grab_focus ();
if (this.text != "") {
@@ -75,7 +72,7 @@ namespace Monitor {
}
if (child_found && needle.length > 0) {
- process_view.expand_all ();
+ process_tree_view.expand_all ();
}
return found || child_found;
diff --git a/src/Widgets/OverallView.vala b/src/Widgets/OverallView.vala
deleted file mode 100644
index 2a4e530c..00000000
--- a/src/Widgets/OverallView.vala
+++ /dev/null
@@ -1,189 +0,0 @@
-
-namespace Monitor {
-
- public class OverallView : Gtk.TreeView {
- private new GenericModel model;
- private Gtk.TreeViewColumn name_column;
- private Gtk.TreeViewColumn cpu_column;
- private Gtk.TreeViewColumn memory_column;
- private Gtk.TreeViewColumn pid_column;
- private Regex? regex;
-
- const string NO_DATA = "\u2014";
-
-
- public OverallView (GenericModel model) {
- this.model = model;
- regex = /(?i:^.*\.(xpm|png)$)/;
-
- // setup name column
- name_column = new Gtk.TreeViewColumn ();
- name_column.title = _("Process Name");
- name_column.expand = true;
- name_column.min_width = 250;
- name_column.set_sort_column_id (Column.NAME);
-
- var icon_cell = new Gtk.CellRendererPixbuf ();
- name_column.pack_start (icon_cell, false);
- // name_column.add_attribute (icon_cell, "icon_name", Column.ICON);
- name_column.set_cell_data_func (icon_cell, icon_cell_layout);
-
- var name_cell = new Gtk.CellRendererText ();
- name_cell.ellipsize = Pango.EllipsizeMode.END;
- name_cell.set_fixed_height_from_font (1);
- name_column.pack_start (name_cell, false);
- name_column.add_attribute (name_cell, "text", Column.NAME);
- insert_column (name_column, -1);
-
- // setup cpu column
- var cpu_cell = new Gtk.CellRendererText ();
- cpu_cell.xalign = 0.5f;
-
- cpu_column = new Gtk.TreeViewColumn.with_attributes (_("CPU"), cpu_cell);
- cpu_column.expand = false;
- cpu_column.set_cell_data_func (cpu_cell, cpu_usage_cell_layout);
- cpu_column.alignment = 0.5f;
- cpu_column.set_sort_column_id (Column.CPU);
- insert_column (cpu_column, -1);
-
- // setup memory column
- var memory_cell = new Gtk.CellRendererText ();
- memory_cell.xalign = 0.5f;
-
- memory_column = new Gtk.TreeViewColumn.with_attributes (_("Memory"), memory_cell);
- memory_column.expand = false;
- memory_column.set_cell_data_func (memory_cell, memory_usage_cell_layout);
- memory_column.alignment = 0.5f;
- memory_column.set_sort_column_id (Column.MEMORY);
- insert_column (memory_column, -1);
-
- // setup PID column
- var pid_cell = new Gtk.CellRendererText ();
- pid_cell.xalign = 0.5f;
- pid_column = new Gtk.TreeViewColumn.with_attributes (_("PID"), pid_cell);
- pid_column.set_cell_data_func (pid_cell, pid_cell_layout);
- pid_column.expand = false;
- pid_column.alignment = 0.5f;
- pid_column.set_sort_column_id (Column.PID);
- pid_column.add_attribute (pid_cell, "text", Column.PID);
- insert_column (pid_column, -1);
-
- // resize all of the columns
- columns_autosize ();
-
- set_model (model);
- }
- public void icon_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer icon_cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
- Value icon_name;
- model.get_value (iter, Column.ICON, out icon_name);
- if (regex.match ((string) icon_name)) {
- string path = ((string) icon_name);
-
- try {
- Gdk.Pixbuf icon = new Gdk.Pixbuf.from_file_at_size (path, 16, -1);
- (icon_cell as Gtk.CellRendererPixbuf).pixbuf = icon;
- } catch (Error e) {
- warning (e.message);
- }
- } else {
- (icon_cell as Gtk.CellRendererPixbuf).icon_name = (string) icon_name;
- }
- }
- public void cpu_usage_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
- // grab the value that was store in the model and convert it down to a usable format
- Value cpu_usage_value;
- model.get_value (iter, Column.CPU, out cpu_usage_value);
- double cpu_usage = cpu_usage_value.get_double ();
-
- // format the double into a string
- if (cpu_usage < 0.0)
- (cell as Gtk.CellRendererText).text = NO_DATA;
- else
- (cell as Gtk.CellRendererText).text = "%.0f%%".printf (cpu_usage * 100.0);
- }
-
- public void memory_usage_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
- // grab the value that was store in the model and convert it down to a usable format
- Value memory_usage_value;
- model.get_value (iter, Column.MEMORY, out memory_usage_value);
- int64 memory_usage = memory_usage_value.get_int64 ();
- double memory_usage_double = (double) memory_usage;
- string units = _("KiB");
-
- // convert to MiB if needed
- if (memory_usage_double > 1024.0) {
- memory_usage_double /= 1024.0;
- units = _("MiB");
- }
-
- // convert to GiB if needed
- if (memory_usage_double > 1024.0) {
- memory_usage_double /= 1024.0;
- units = _("GiB");
- }
-
- // format the double into a string
- if (memory_usage == 0)
- (cell as Gtk.CellRendererText).text = NO_DATA;
- else
- (cell as Gtk.CellRendererText).text = "%.1f %s".printf (memory_usage_double, units);
- }
-
- private void pid_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
- Value pid_value;
- model.get_value (iter, Column.PID, out pid_value);
- int pid = pid_value.get_int ();
- // format the double into a string
- if (pid == 0) {
- (cell as Gtk.CellRendererText).text = NO_DATA;
- }
- }
-
- public void focus_on_first_row () {
- Gtk.TreePath tree_path = new Gtk.TreePath.from_indices (0);
- this.set_cursor (tree_path, null, false);
- grab_focus ();
- }
-
- public void focus_on_child_row () {
- Gtk.TreePath tree_path = new Gtk.TreePath.from_indices (0, 0);
- this.set_cursor (tree_path, null, false);
- grab_focus ();
- }
-
- public int get_pid_of_selected () {
- Gtk.TreeIter iter;
- Gtk.TreeModel model;
- int pid = 0;
- var selection = this.get_selection ().get_selected_rows(out model).nth_data(0);
- model.get_iter (out iter, selection);
- model.get (iter, Column.PID, out pid);
- return pid;
- }
-
- // How about GtkTreeSelection ?
-
- public void expanded () {
- Gtk.TreeModel model;
- var selection = this.get_selection ().get_selected_rows(out model).nth_data(0);
- this.expand_row (selection, false);
- }
-
- public void collapse () {
- Gtk.TreeModel model;
- var selection = this.get_selection ().get_selected_rows(out model).nth_data(0);
- this.collapse_row (selection);
- }
-
- public void kill_process () {
- int pid = get_pid_of_selected ();
- model.kill_process (pid);
- }
-
- public void end_process () {
- int pid = get_pid_of_selected ();
- model.end_process (pid);
- }
-
- }
-}
diff --git a/src/Widgets/Statusbar/Statusbar.vala b/src/Widgets/Statusbar/Statusbar.vala
index 1f35151d..2689bcc9 100644
--- a/src/Widgets/Statusbar/Statusbar.vala
+++ b/src/Widgets/Statusbar/Statusbar.vala
@@ -6,7 +6,7 @@ public class Monitor.Statusbar : Gtk.ActionBar {
construct {
var cpu_icon = new Gtk.Image.from_icon_name ("cpu-symbolic", Gtk.IconSize.SMALL_TOOLBAR);
cpu_icon.tooltip_text = _ ("CPU");
-
+
var ram_icon = new Gtk.Image.from_icon_name ("ram-symbolic", Gtk.IconSize.SMALL_TOOLBAR);
ram_icon.tooltip_text = _ ("Memory");
@@ -35,6 +35,9 @@ public class Monitor.Statusbar : Gtk.ActionBar {
cpu_usage_label.set_text (("%d%%").printf (sysres.cpu_percentage));
memory_usage_label.set_text (("%d%%").printf (sysres.memory_percentage));
+ string cpu_tooltip_text = ("%.2f %s").printf (sysres.cpu_frequency, _ ("GHz"));
+ cpu_usage_label.tooltip_text = cpu_tooltip_text;
+
string memory_tooltip_text = ("%.1f %s / %.1f %s").printf (sysres.memory_used, _ ("GiB"), sysres.memory_total, _ ("GiB"));
memory_usage_label.tooltip_text = memory_tooltip_text;
diff --git a/subprojects/live-chart.wrap b/subprojects/live-chart.wrap
new file mode 100644
index 00000000..aa29f7e5
--- /dev/null
+++ b/subprojects/live-chart.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/lcallarec/live-chart.git
+revision = head