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 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + 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