summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:59:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:59:44 +0000
commitfb31765cbe33890f325a87015507364156741321 (patch)
tree0c5cd12aee0a0a6a6e2d542520df5846859bd40d /src
parentInitial commit. (diff)
downloadgnome-system-monitor-fb31765cbe33890f325a87015507364156741321.tar.xz
gnome-system-monitor-fb31765cbe33890f325a87015507364156741321.zip
Adding upstream version 42.0.upstream/42.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r--src/application.cpp545
-rw-r--r--src/application.h258
-rw-r--r--src/argv.cpp44
-rw-r--r--src/argv.h23
-rw-r--r--src/cgroups.cpp117
-rw-r--r--src/cgroups.h10
-rw-r--r--src/defaulttable.h53
-rw-r--r--src/disks.cpp491
-rw-r--r--src/disks.h14
-rw-r--r--src/gsm.gresource.xml14
-rw-r--r--src/gsm_gksu.cpp55
-rw-r--r--src/gsm_gksu.h13
-rw-r--r--src/gsm_gnomesu.cpp41
-rw-r--r--src/gsm_gnomesu.h13
-rw-r--r--src/gsm_pkexec.cpp38
-rw-r--r--src/gsm_pkexec.h13
-rw-r--r--src/interface.cpp887
-rw-r--r--src/interface.h30
-rw-r--r--src/legacy/e_date.c215
-rw-r--r--src/legacy/e_date.h15
-rw-r--r--src/legacy/gsm_color_button.c859
-rw-r--r--src/legacy/gsm_color_button.h77
-rw-r--r--src/legacy/meson.build16
-rw-r--r--src/legacy/treeview.c289
-rw-r--r--src/legacy/treeview.h43
-rw-r--r--src/load-graph.cpp1119
-rw-r--r--src/load-graph.h146
-rw-r--r--src/lsof.cpp316
-rw-r--r--src/lsof.h10
-rw-r--r--src/main.cpp39
-rw-r--r--src/memmaps.cpp491
-rw-r--r--src/memmaps.h10
-rw-r--r--src/meson.build110
-rw-r--r--src/openfiles.cpp371
-rw-r--r--src/openfiles.h11
-rw-r--r--src/org.gnome.gnome-system-monitor.gschema.xml.in776
-rw-r--r--src/prefsdialog.cpp361
-rw-r--r--src/prefsdialog.h10
-rw-r--r--src/prettytable.cpp337
-rw-r--r--src/prettytable.h74
-rw-r--r--src/procactions.cpp187
-rw-r--r--src/procactions.h33
-rw-r--r--src/procdialogs.cpp336
-rw-r--r--src/procdialogs.h53
-rw-r--r--src/procproperties.cpp251
-rw-r--r--src/procproperties.h30
-rw-r--r--src/proctable.cpp1280
-rw-r--r--src/proctable.h75
-rw-r--r--src/selinux.cpp66
-rw-r--r--src/selinux.h15
-rw-r--r--src/setaffinity.cpp473
-rw-r--r--src/setaffinity.h21
-rw-r--r--src/settings-keys.h45
-rw-r--r--src/smooth_refresh.cpp151
-rw-r--r--src/smooth_refresh.h103
-rw-r--r--src/systemd.cpp61
-rw-r--r--src/systemd.h12
-rw-r--r--src/util.cpp766
-rw-r--r--src/util.h177
59 files changed, 12489 insertions, 0 deletions
diff --git a/src/application.cpp b/src/application.cpp
new file mode 100644
index 0000000..f23d29a
--- /dev/null
+++ b/src/application.cpp
@@ -0,0 +1,545 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <glibtop.h>
+#include <glibtop/close.h>
+#include <glibtop/cpu.h>
+#include <glibtop/sysinfo.h>
+#include <handy.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include "application.h"
+#include "procdialogs.h"
+#include "prefsdialog.h"
+#include "interface.h"
+#include "proctable.h"
+#include "load-graph.h"
+#include "settings-keys.h"
+#include "argv.h"
+#include "util.h"
+#include "lsof.h"
+#include "disks.h"
+
+static void
+cb_solaris_mode_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.solaris_mode = settings.get_boolean(key);
+ app->cpu_graph->clear_background();
+ if (app->timeout) {
+ proctable_update (app);
+ }
+}
+
+static void
+cb_process_memory_in_iec_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app) {
+ app->config.process_memory_in_iec = settings.get_boolean(key);
+ app->cpu_graph->clear_background();
+ if (app->timeout) {
+ proctable_update(app);
+ }
+}
+
+static void
+cb_logarithmic_scale_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.logarithmic_scale = settings.get_boolean(key);
+ app->mem_graph->clear_background();
+ load_graph_reset(app->mem_graph);
+}
+
+static void
+cb_draw_stacked_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.draw_stacked = settings.get_boolean(key);
+ app->cpu_graph->clear_background();
+ load_graph_reset(app->cpu_graph);
+}
+
+static void
+cb_draw_smooth_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.draw_smooth = settings.get_boolean(key);
+ app->cpu_graph->clear_background();
+ app->mem_graph->clear_background();
+ app->net_graph->clear_background();
+ load_graph_reset(app->cpu_graph);
+ load_graph_reset(app->mem_graph);
+ load_graph_reset(app->net_graph);
+}
+
+static void
+cb_resources_memory_in_iec_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.resources_memory_in_iec = settings.get_boolean(key);
+ app->cpu_graph->clear_background();
+ if (app->timeout) {
+ proctable_update (app);
+ }
+}
+
+static void
+cb_network_in_bits_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.network_in_bits = settings.get_boolean(key);
+ if (app->config.network_total_unit == FALSE) {
+ app->config.network_total_in_bits = app->config.network_in_bits;
+ }
+ // force scale to be redrawn
+ app->net_graph->clear_background();
+}
+
+static void
+cb_network_total_in_unit_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.network_total_unit = settings.get_boolean(key);
+ if (app->config.network_total_unit == FALSE) {
+ app->config.network_total_in_bits = app->config.network_in_bits;
+ } else {
+ app->config.network_total_in_bits = app->settings->get_boolean (GSM_SETTING_NETWORK_TOTAL_IN_BITS);
+ }
+ // force scale to be redrawn
+ app->net_graph->clear_background();
+}
+
+static void
+cb_network_total_in_bits_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ app->config.network_total_in_bits = settings.get_boolean(key);
+ // force scale to be redrawn
+ app->net_graph->clear_background();
+}
+
+static void
+cb_timeouts_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ if (key == GSM_SETTING_PROCESS_UPDATE_INTERVAL) {
+ app->config.update_interval = settings.get_int (key);
+
+ app->smooth_refresh->reset();
+ if (app->timeout) {
+ proctable_reset_timeout (app);
+ }
+ } else if (key == GSM_SETTING_GRAPH_UPDATE_INTERVAL) {
+ app->config.graph_update_interval = settings.get_int (key);
+ load_graph_change_speed(app->cpu_graph,
+ app->config.graph_update_interval);
+ load_graph_change_speed(app->mem_graph,
+ app->config.graph_update_interval);
+ load_graph_change_speed(app->net_graph,
+ app->config.graph_update_interval);
+ } else if (key == GSM_SETTING_DISKS_UPDATE_INTERVAL) {
+ app->config.disks_update_interval = settings.get_int (key);
+ disks_reset_timeout (app);
+ }
+}
+
+static void
+cb_data_points_changed(Gio::Settings& settings, Glib::ustring key, GsmApplication* app) {
+ app->config.graph_data_points = settings.get_int (key);
+ unsigned points = app->config.graph_data_points + 2;
+ load_graph_change_num_points(app->cpu_graph, points);
+ load_graph_change_num_points(app->mem_graph, points);
+ load_graph_change_num_points(app->net_graph, points);
+}
+
+static void
+apply_cpu_color_settings(Gio::Settings& settings, GsmApplication* app)
+{
+ GVariant *cpu_colors_var = g_settings_get_value (settings.gobj (), GSM_SETTING_CPU_COLORS);
+ gsize n = g_variant_n_children(cpu_colors_var);
+
+ gchar *color;
+
+ // Create builder to add the new colors if user has more than the number of cores with defaults.
+ GVariantBuilder builder;
+ GVariant* child;
+ GVariant* full;
+
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ std::vector<std::string> random_colors = procman::generate_colors(app->config.num_cpus);
+
+ for (guint i = 0; i < static_cast<guint>(app->config.num_cpus); i++) {
+ if(i < n) {
+ child = g_variant_get_child_value ( cpu_colors_var, i );
+ g_variant_get_child( child, 1, "s", &color);
+ g_variant_builder_add_value ( &builder, child);
+ g_variant_unref (child);
+ } else {
+ color = g_strdup (random_colors[i].c_str());
+ g_variant_builder_add(&builder, "(us)", i, color);
+ }
+ gdk_rgba_parse(&app->config.cpu_color[i], color);
+ g_free (color);
+ }
+ full = g_variant_builder_end(&builder);
+ // if the user has more cores than colors stored in the gsettings, store the newly built gvariant in gsettings
+ if (n < static_cast<guint>(app->config.num_cpus)) {
+ g_settings_set_value(settings.gobj (), GSM_SETTING_CPU_COLORS, full);
+ } else {
+ g_variant_unref(full);
+ }
+
+ g_variant_unref(cpu_colors_var);
+}
+
+static void
+cb_color_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app)
+{
+ if (key == GSM_SETTING_CPU_COLORS) {
+ apply_cpu_color_settings(settings, app);
+ for (int i = 0; i < app->config.num_cpus; i++) {
+ if(!gdk_rgba_equal(&app->cpu_graph->colors[i], &app->config.cpu_color[i])) {
+ app->cpu_graph->colors[i] = app->config.cpu_color[i];
+ break;
+ }
+ }
+ return;
+ }
+
+ auto color = settings.get_string (key);
+ if (key == GSM_SETTING_MEM_COLOR) {
+ gdk_rgba_parse (&app->config.mem_color, color.c_str ());
+ app->mem_graph->colors.at(0) = app->config.mem_color;
+ } else if (key == GSM_SETTING_SWAP_COLOR) {
+ gdk_rgba_parse (&app->config.swap_color, color.c_str ());
+ app->mem_graph->colors.at(1) = app->config.swap_color;
+ } else if (key == GSM_SETTING_NET_IN_COLOR) {
+ gdk_rgba_parse (&app->config.net_in_color, color.c_str ());
+ app->net_graph->colors.at(0) = app->config.net_in_color;
+ } else if (key == GSM_SETTING_NET_OUT_COLOR) {
+ gdk_rgba_parse (&app->config.net_out_color, color.c_str ());
+ app->net_graph->colors.at(1) = app->config.net_out_color;
+ }
+}
+
+void
+GsmApplication::load_settings()
+{
+ glibtop_cpu cpu;
+
+ this->settings = Gio::Settings::create (GSM_GSETTINGS_SCHEMA);
+
+ config.solaris_mode = this->settings->get_boolean (GSM_SETTING_SOLARIS_MODE);
+ this->settings->signal_changed(GSM_SETTING_SOLARIS_MODE).connect ([this](const Glib::ustring& key) {
+ cb_solaris_mode_changed (*this->settings.operator->(), key, this);
+ });
+
+ config.process_memory_in_iec = this->settings->get_boolean (GSM_SETTING_PROCESS_MEMORY_IN_IEC);
+ this->settings->signal_changed (GSM_SETTING_PROCESS_MEMORY_IN_IEC).connect ([this](const Glib::ustring& key) {
+ cb_process_memory_in_iec_changed (*this->settings.operator->(), key, this);
+ });
+
+ config.logarithmic_scale = this->settings->get_boolean (GSM_SETTING_LOGARITHMIC_SCALE);
+ this->settings->signal_changed(GSM_SETTING_LOGARITHMIC_SCALE).connect ([this](const Glib::ustring& key) {
+ cb_logarithmic_scale_changed (*this->settings.operator->(), key, this);
+ });
+
+ config.draw_stacked = this->settings->get_boolean (GSM_SETTING_DRAW_STACKED);
+ this->settings->signal_changed(GSM_SETTING_DRAW_STACKED).connect ([this](const Glib::ustring& key) {
+ cb_draw_stacked_changed (*this->settings.operator->(), key, this);
+ });
+
+ config.draw_smooth = this->settings->get_boolean (GSM_SETTING_DRAW_SMOOTH);
+ this->settings->signal_changed(GSM_SETTING_DRAW_SMOOTH).connect ([this](const Glib::ustring& key) {
+ cb_draw_smooth_changed (*this->settings.operator->(), key, this);
+ });
+
+ config.resources_memory_in_iec = this->settings->get_boolean (GSM_SETTING_RESOURCES_MEMORY_IN_IEC);
+ this->settings->signal_changed (GSM_SETTING_RESOURCES_MEMORY_IN_IEC).connect ([this](const Glib::ustring& key) {
+ cb_resources_memory_in_iec_changed (*this->settings.operator->(), key, this);
+ });
+
+ config.network_in_bits = this->settings->get_boolean (GSM_SETTING_NETWORK_IN_BITS);
+ this->settings->signal_changed (GSM_SETTING_NETWORK_IN_BITS).connect ([this](const Glib::ustring& key) {
+ cb_network_in_bits_changed (*this->settings.operator->(), key, this);
+ });
+
+ config.network_total_unit = this->settings->get_boolean (GSM_SETTING_NETWORK_TOTAL_UNIT);
+ this->settings->signal_changed (GSM_SETTING_NETWORK_TOTAL_UNIT).connect ([this](const Glib::ustring& key) {
+ cb_network_total_in_unit_changed (*this->settings.operator->(), key, this);
+ });
+
+ if (config.network_total_unit == FALSE) {
+ config.network_total_in_bits = config.network_in_bits;
+ this->settings->set_boolean (GSM_SETTING_NETWORK_TOTAL_IN_BITS, config.network_in_bits);
+ } else {
+ config.network_total_in_bits = this->settings->get_boolean (GSM_SETTING_NETWORK_TOTAL_IN_BITS);
+ }
+ this->settings->signal_changed (GSM_SETTING_NETWORK_TOTAL_IN_BITS).connect ([this](const Glib::ustring& key) {
+ cb_network_total_in_bits_changed (*this->settings.operator->(), key, this);
+ });
+
+ auto cbtc = [this](const Glib::ustring& key) { cb_timeouts_changed(*this->settings.operator->(), key, this); };
+ config.update_interval = this->settings->get_int (GSM_SETTING_PROCESS_UPDATE_INTERVAL);
+ this->settings->signal_changed (GSM_SETTING_PROCESS_UPDATE_INTERVAL).connect (cbtc);
+ config.graph_update_interval = this->settings->get_int (GSM_SETTING_GRAPH_UPDATE_INTERVAL);
+ this->settings->signal_changed (GSM_SETTING_GRAPH_UPDATE_INTERVAL).connect (cbtc);
+ config.disks_update_interval = this->settings->get_int (GSM_SETTING_DISKS_UPDATE_INTERVAL);
+ this->settings->signal_changed (GSM_SETTING_DISKS_UPDATE_INTERVAL).connect (cbtc);
+
+ config.graph_data_points = this->settings->get_int (GSM_SETTING_GRAPH_DATA_POINTS);
+ this->settings->signal_changed (GSM_SETTING_GRAPH_DATA_POINTS).connect ([this](const Glib::ustring& key) {
+ cb_data_points_changed (*this->settings.operator->(), key, this);
+ });
+
+ glibtop_get_cpu (&cpu);
+ frequency = cpu.frequency;
+
+ config.num_cpus = glibtop_get_sysinfo()->ncpu; // or server->ncpu + 1
+
+ apply_cpu_color_settings (*this->settings.operator->(), this);
+
+ auto mem_color = this->settings->get_string (GSM_SETTING_MEM_COLOR);
+ gdk_rgba_parse (&config.mem_color, mem_color.empty () ? "#000000ff0082" : mem_color.c_str ());
+
+ auto swap_color = this->settings->get_string (GSM_SETTING_SWAP_COLOR);
+ gdk_rgba_parse (&config.swap_color, swap_color.empty () ? "#00b6000000ff" : swap_color.c_str ());
+
+ auto net_in_color = this->settings->get_string (GSM_SETTING_NET_IN_COLOR);
+ gdk_rgba_parse (&config.net_in_color, net_in_color.empty () ? "#000000f200f2" : net_in_color.c_str ());
+
+ auto net_out_color = this->settings->get_string (GSM_SETTING_NET_OUT_COLOR);
+ gdk_rgba_parse (&config.net_out_color, net_out_color.empty () ? "#00f2000000c1" : net_out_color.c_str ());
+
+ auto cbcc = [this](const Glib::ustring& key) { cb_color_changed(*this->settings.operator->(), key, this); };
+ for (auto k : {GSM_SETTING_CPU_COLORS, GSM_SETTING_MEM_COLOR, GSM_SETTING_SWAP_COLOR, GSM_SETTING_NET_IN_COLOR, GSM_SETTING_NET_OUT_COLOR}) {
+ this->settings->signal_changed (k).connect(cbcc);
+ }
+}
+
+
+GsmApplication::GsmApplication()
+ : Gtk::Application("org.gnome.SystemMonitor", Gio::APPLICATION_HANDLES_COMMAND_LINE),
+ tree(NULL),
+ proc_actionbar_revealer(NULL),
+ popup_menu(NULL),
+ disk_list(NULL),
+ stack(NULL),
+ refresh_button(NULL),
+ process_menu_button(NULL),
+ window_menu_button(NULL),
+ end_process_button(NULL),
+ search_button(NULL),
+ search_entry(NULL),
+ search_bar(NULL),
+ config(),
+ cpu_graph(NULL),
+ mem_graph(NULL),
+ net_graph(NULL),
+ cpu_label_fixed_width(0),
+ net_label_fixed_width(0),
+ selection(NULL),
+ timeout(0U),
+ disk_timeout(0U),
+ top_of_tree(NULL),
+ last_vscroll_max(0.0),
+ last_vscroll_value(0.0),
+ pretty_table(NULL),
+ settings(NULL),
+ main_window(NULL),
+ frequency(0U),
+ smooth_refresh(NULL),
+ cpu_total_time(0ULL),
+ cpu_total_time_last(0ULL)
+{
+ Glib::set_application_name(_("System Monitor"));
+}
+
+Glib::RefPtr<GsmApplication> GsmApplication::get ()
+{
+ static Glib::RefPtr<GsmApplication> singleton;
+
+ if (!singleton) {
+ singleton = Glib::RefPtr<GsmApplication>(new GsmApplication());
+ }
+ return singleton;
+}
+
+void GsmApplication::on_activate()
+{
+ gtk_window_present (GTK_WINDOW (main_window));
+}
+
+void
+GsmApplication::save_config ()
+{
+ int width, height, xpos, ypos;
+ gboolean maximized;
+
+ gtk_window_get_size (GTK_WINDOW (main_window), &width, &height);
+ gtk_window_get_position (GTK_WINDOW (main_window), &xpos, &ypos);
+
+ maximized = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (main_window))) & GDK_WINDOW_STATE_MAXIMIZED;
+
+ g_settings_set (settings->gobj (), GSM_SETTING_WINDOW_STATE, "(iiii)",
+ width, height, xpos, ypos);
+
+ settings->set_boolean (GSM_SETTING_MAXIMIZED, maximized);
+}
+
+int GsmApplication::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>& command_line)
+{
+ int argc = 0;
+ char** argv = command_line->get_arguments(argc);
+
+ Glib::OptionContext context;
+ context.set_summary(_("A simple process and system monitor."));
+ context.set_ignore_unknown_options(true);
+
+ Glib::OptionGroup gtkgroup(gtk_get_option_group(true));
+ procman::OptionGroup option_group;
+
+ context.set_main_group(option_group);
+ context.add_group (gtkgroup);
+
+ try {
+ context.parse(argc, argv);
+ } catch (const Glib::Error& ex) {
+ g_error("Arguments parse error : %s", ex.what().c_str());
+ }
+
+ g_strfreev(argv);
+
+ if (option_group.print_version) {
+ g_print("%s %s\n", _("GNOME System Monitor"), VERSION);
+ exit (EXIT_SUCCESS);
+ }
+
+ if (option_group.show_processes_tab)
+ this->settings->set_string (GSM_SETTING_CURRENT_TAB, "processes");
+ else if (option_group.show_resources_tab)
+ this->settings->set_string (GSM_SETTING_CURRENT_TAB, "resources");
+ else if (option_group.show_file_systems_tab)
+ this->settings->set_string (GSM_SETTING_CURRENT_TAB, "disks");
+ else if (option_group.print_version)
+
+ on_activate ();
+
+ return 0;
+}
+
+void
+GsmApplication::on_help_activate(const Glib::VariantBase&)
+{
+ GError* error = 0;
+ if (!g_app_info_launch_default_for_uri("help:gnome-system-monitor", NULL, &error)) {
+ g_warning("Could not display help : %s", error->message);
+ g_error_free(error);
+ }
+}
+
+void
+GsmApplication::on_lsof_activate(const Glib::VariantBase&)
+{
+ procman_lsof(this);
+}
+
+void
+GsmApplication::on_preferences_activate(const Glib::VariantBase&)
+{
+ create_preferences_dialog (this);
+}
+
+void
+GsmApplication::on_quit_activate(const Glib::VariantBase&)
+{
+ shutdown ();
+}
+
+void
+GsmApplication::shutdown()
+{
+ save_config ();
+
+ if (timeout)
+ g_source_remove (timeout);
+ if (disk_timeout)
+ g_source_remove (disk_timeout);
+
+ proctable_free_table (this);
+ delete smooth_refresh;
+ delete pretty_table;
+
+ glibtop_close();
+
+ quit();
+}
+
+void GsmApplication::on_startup()
+{
+ HdyStyleManager *style_manager;
+
+ Gtk::Application::on_startup();
+
+ hdy_init();
+
+ load_resources ();
+
+ style_manager = hdy_style_manager_get_default ();
+ hdy_style_manager_set_color_scheme (style_manager, HDY_COLOR_SCHEME_PREFER_LIGHT);
+
+ Glib::RefPtr<Gio::SimpleAction> action;
+
+ action = Gio::SimpleAction::create("quit");
+ action->signal_activate().connect(sigc::mem_fun(*this, &GsmApplication::on_quit_activate));
+ add_action(action);
+
+ action = Gio::SimpleAction::create("help");
+ action->signal_activate().connect(sigc::mem_fun(*this, &GsmApplication::on_help_activate));
+ add_action(action);
+
+ action = Gio::SimpleAction::create("lsof");
+ action->signal_activate().connect(sigc::mem_fun(*this, &GsmApplication::on_lsof_activate));
+ add_action(action);
+
+ action = Gio::SimpleAction::create("preferences");
+ action->signal_activate().connect(sigc::mem_fun(*this, &GsmApplication::on_preferences_activate));
+ add_action(action);
+
+ Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_resource("/org/gnome/gnome-system-monitor/data/menus.ui");
+
+ Glib::RefPtr<Gio::Menu> menu = Glib::RefPtr<Gio::Menu>::cast_static(builder->get_object ("app-menu"));
+ set_app_menu (menu);
+
+ add_accelerator("<Primary>d", "win.show-dependencies", NULL);
+ add_accelerator("<Primary>q", "app.quit", NULL);
+ add_accelerator("<Primary>s", "win.send-signal-stop", g_variant_new_int32 (SIGSTOP));
+ add_accelerator("<Primary>c", "win.send-signal-cont", g_variant_new_int32 (SIGCONT));
+ add_accelerator("<Primary>e", "win.send-signal-end", g_variant_new_int32 (SIGTERM));
+ add_accelerator("<Primary>k", "win.send-signal-kill", g_variant_new_int32 (SIGKILL));
+ add_accelerator("<Alt>s", "win.set-affinity", NULL);
+ add_accelerator("<Primary>m", "win.memory-maps", NULL);
+ add_accelerator("<Primary>o", "win.open-files", NULL);
+ add_accelerator("<Alt>Return", "win.process-properties", NULL);
+ add_accelerator("<Primary>f", "win.search", g_variant_new_boolean (TRUE));
+ add_accelerator("F1", "app.help", NULL);
+
+ Gtk::Window::set_default_icon_name ("org.gnome.SystemMonitor");
+
+ glibtop_init ();
+
+ load_settings ();
+
+ pretty_table = new PrettyTable();
+ smooth_refresh = new SmoothRefresh(settings);
+
+ create_main_window (this);
+
+ add_accelerator ("<Alt>1", "win.show-page", g_variant_new_string ("processes"));
+ add_accelerator ("<Alt>2", "win.show-page", g_variant_new_string ("resources"));
+ add_accelerator ("<Alt>3", "win.show-page", g_variant_new_string ("disks"));
+ add_accelerator ("<Primary>r", "win.refresh", NULL);
+
+ gtk_widget_show (GTK_WIDGET (main_window));
+}
+
+
+void GsmApplication::load_resources()
+{
+ auto res = Gio::Resource::create_from_file(GSM_RESOURCE_FILE);
+ res->register_global();
+}
+
diff --git a/src/application.h b/src/application.h
new file mode 100644
index 0000000..455a699
--- /dev/null
+++ b/src/application.h
@@ -0,0 +1,258 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_APPLICATION_H_
+#define _GSM_APPLICATION_H_
+
+#include <gtkmm.h>
+#include <glibtop/cpu.h>
+#include <handy.h>
+
+#include <algorithm>
+
+struct LoadGraph;
+
+#include "smooth_refresh.h"
+#include "prettytable.h"
+#include "legacy/treeview.h"
+#include "util.h"
+
+static const unsigned MIN_UPDATE_INTERVAL = 1 * 1000;
+static const unsigned MAX_UPDATE_INTERVAL = 100 * 1000;
+
+
+enum ProcmanTab
+{
+ PROCMAN_TAB_PROCESSES,
+ PROCMAN_TAB_RESOURCES,
+ PROCMAN_TAB_DISKS
+};
+
+
+struct ProcConfig
+ : private procman::NonCopyable
+{
+ ProcConfig()
+ : update_interval(0),
+ graph_update_interval(0),
+ disks_update_interval(0),
+ graph_data_points(0),
+ mem_color(),
+ swap_color(),
+ net_in_color(),
+ net_out_color(),
+ bg_color(),
+ frame_color(),
+ num_cpus(0),
+ solaris_mode(false),
+ process_memory_in_iec(true),
+ logarithmic_scale(false),
+ draw_stacked(false),
+ draw_smooth(true),
+ resources_memory_in_iec(true),
+ network_in_bits(false),
+ network_total_unit(false),
+ network_total_in_bits(false)
+ {
+ std::fill(&this->cpu_color[0], &this->cpu_color[GLIBTOP_NCPU], GdkRGBA());
+ }
+
+ int update_interval;
+ int graph_update_interval;
+ int disks_update_interval;
+ int graph_data_points;
+ GdkRGBA cpu_color[GLIBTOP_NCPU];
+ GdkRGBA mem_color;
+ GdkRGBA swap_color;
+ GdkRGBA net_in_color;
+ GdkRGBA net_out_color;
+ GdkRGBA bg_color;
+ GdkRGBA frame_color;
+ gint num_cpus;
+ bool solaris_mode;
+ bool process_memory_in_iec;
+ bool logarithmic_scale;
+ bool draw_stacked;
+ bool draw_smooth;
+ bool resources_memory_in_iec;
+ bool network_in_bits;
+ bool network_total_unit;
+ bool network_total_in_bits;
+};
+
+
+
+struct MutableProcInfo
+ : private procman::NonCopyable
+{
+
+MutableProcInfo()
+ : vmsize(0UL),
+ memres(0UL),
+ memshared(0UL),
+ memwritable(0UL),
+ mem(0UL),
+#ifdef HAVE_WNCK
+ memxserver(0UL),
+#endif
+ start_time(0UL),
+ cpu_time(0ULL),
+ disk_read_bytes_total(0ULL),
+ disk_write_bytes_total(0ULL),
+ disk_read_bytes_current(0ULL),
+ disk_write_bytes_current(0ULL),
+ status(0U),
+ pcpu(0),
+ nice(0)
+ {
+ }
+
+ std::string user;
+
+ std::string wchan;
+
+ // all these members are filled with libgtop which uses
+ // guint64 (to have fixed size data) but we don't need more
+ // than an unsigned long (even for 32bit apps on a 64bit
+ // kernel) as these data are amounts, not offsets.
+ gulong vmsize;
+ gulong memres;
+ gulong memshared;
+ gulong memwritable;
+ gulong mem;
+
+#ifdef HAVE_WNCK
+ // wnck gives an unsigned long
+ gulong memxserver;
+#endif
+
+ gulong start_time;
+ guint64 cpu_time;
+ guint64 disk_read_bytes_total;
+ guint64 disk_write_bytes_total;
+ guint64 disk_read_bytes_current;
+ guint64 disk_write_bytes_current;
+ guint status;
+ gdouble pcpu;
+ gint nice;
+ std::string cgroup_name;
+
+ std::string unit;
+ std::string session;
+ std::string seat;
+
+ std::string owner;
+};
+
+
+class ProcInfo
+: public MutableProcInfo
+{
+ public:
+ ProcInfo& operator=(const ProcInfo&) = delete;
+ ProcInfo(const ProcInfo&) = delete;
+ ProcInfo(pid_t pid);
+ // adds one more ref to icon
+ void set_icon(Glib::RefPtr<Gdk::Pixbuf> icon);
+ void set_user(guint uid);
+ std::string lookup_user(guint uid);
+
+ GtkTreeIter node;
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf;
+ std::string tooltip;
+ std::string name;
+ std::string arguments;
+
+ std::string security_context;
+
+ const pid_t pid;
+ pid_t ppid;
+ guint uid;
+};
+
+class ProcList {
+ // TODO: use a set instead
+ // sorted by pid. The map has a nice property : it is sorted
+ // by pid so this helps a lot when looking for the parent node
+ // as ppid is nearly always < pid.
+ typedef std::map<pid_t, ProcInfo> List;
+ List data;
+ std::mutex data_lock;
+public:
+ std::map<pid_t, unsigned long> cpu_times;
+ typedef List::iterator Iterator;
+ Iterator begin() { return std::begin(data); }
+ Iterator end() { return std::end(data); }
+ Iterator erase(Iterator it) {
+ std::lock_guard<std::mutex> lg(data_lock);
+ return data.erase(it);
+ }
+ ProcInfo* add(pid_t pid) { return &data.emplace(std::piecewise_construct, std::forward_as_tuple(pid), std::forward_as_tuple(pid)).first->second; }
+ void clear() { return data.clear(); }
+
+ ProcInfo* find(pid_t pid);
+};
+
+class GsmApplication : public Gtk::Application, private procman::NonCopyable
+
+{
+private:
+ void load_settings();
+ void load_resources();
+
+ void on_preferences_activate(const Glib::VariantBase&);
+ void on_lsof_activate(const Glib::VariantBase&);
+ void on_help_activate(const Glib::VariantBase&);
+ void on_quit_activate(const Glib::VariantBase&);
+protected:
+ GsmApplication();
+public:
+ static Glib::RefPtr<GsmApplication> get ();
+
+ void save_config();
+ void shutdown();
+
+ ProcList processes;
+ GsmTreeView *tree;
+ GtkRevealer *proc_actionbar_revealer;
+ GtkMenu *popup_menu;
+ GsmTreeView *disk_list;
+ GtkStack *stack;
+ GtkButton *refresh_button;
+ GtkMenuButton *process_menu_button;
+ GtkMenuButton *window_menu_button;
+ GtkButton *end_process_button;
+ GtkButton *search_button;
+ GtkSearchEntry *search_entry;
+ GtkSearchBar *search_bar;
+ ProcConfig config;
+ LoadGraph *cpu_graph;
+ LoadGraph *mem_graph;
+ LoadGraph *net_graph;
+ gint cpu_label_fixed_width;
+ gint net_label_fixed_width;
+ GtkTreeSelection *selection;
+ guint timeout;
+ guint disk_timeout;
+
+ GtkTreePath *top_of_tree;
+ gdouble last_vscroll_max;
+ gdouble last_vscroll_value;
+
+ PrettyTable *pretty_table;
+
+ Glib::RefPtr<Gio::Settings> settings;
+ HdyApplicationWindow *main_window;
+
+ unsigned frequency;
+
+ SmoothRefresh *smooth_refresh;
+
+ guint64 cpu_total_time;
+ guint64 cpu_total_time_last;
+
+protected:
+ virtual void on_activate();
+ virtual int on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>& command_line);
+ virtual void on_startup();
+};
+
+#endif /* _GSM_APPLICATION_H_ */
diff --git a/src/argv.cpp b/src/argv.cpp
new file mode 100644
index 0000000..b439071
--- /dev/null
+++ b/src/argv.cpp
@@ -0,0 +1,44 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <glibmm/optionentry.h>
+
+#include "argv.h"
+
+namespace procman
+{
+ OptionGroup::OptionGroup()
+ : Glib::OptionGroup("", ""),
+ show_system_tab(false),
+ show_processes_tab(false),
+ show_resources_tab(false),
+ show_file_systems_tab(false),
+ print_version(false)
+ {
+ Glib::OptionEntry proc_tab;
+ proc_tab.set_long_name("show-processes-tab");
+ proc_tab.set_short_name('p');
+ proc_tab.set_description(_("Show the Processes tab"));
+
+ Glib::OptionEntry res_tab;
+ res_tab.set_long_name("show-resources-tab");
+ res_tab.set_short_name('r');
+ res_tab.set_description(_("Show the Resources tab"));
+
+ Glib::OptionEntry fs_tab;
+ fs_tab.set_long_name("show-file-systems-tab");
+ fs_tab.set_short_name('f');
+ fs_tab.set_description(_("Show the File Systems tab"));
+
+ Glib::OptionEntry show_version;
+ show_version.set_long_name("version");
+ show_version.set_description(_("Show the application’s version"));
+
+ this->add_entry(proc_tab, this->show_processes_tab);
+ this->add_entry(res_tab, this->show_resources_tab);
+ this->add_entry(fs_tab, this->show_file_systems_tab);
+ this->add_entry(show_version, this->print_version);
+ }
+}
+
diff --git a/src/argv.h b/src/argv.h
new file mode 100644
index 0000000..a1a8dd2
--- /dev/null
+++ b/src/argv.h
@@ -0,0 +1,23 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_ARGV_H_
+#define _GSM_ARGV_H_
+
+#include <glibmm/optiongroup.h>
+
+namespace procman
+{
+ class OptionGroup
+ : public Glib::OptionGroup
+ {
+ public:
+ OptionGroup();
+
+ bool show_system_tab;
+ bool show_processes_tab;
+ bool show_resources_tab;
+ bool show_file_systems_tab;
+ bool print_version;
+ };
+}
+
+#endif /* _GSM_ARGV_H_ */
diff --git a/src/cgroups.cpp b/src/cgroups.cpp
new file mode 100644
index 0000000..8091195
--- /dev/null
+++ b/src/cgroups.cpp
@@ -0,0 +1,117 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+#include "cgroups.h"
+#include "util.h"
+
+#include <map>
+#include <unordered_map>
+#include <utility>
+
+bool
+cgroups_enabled(void)
+{
+ static bool initialized = false;
+ static bool has_cgroups;
+
+ if (not initialized) {
+ initialized = true;
+ has_cgroups = Glib::file_test("/proc/cgroups", Glib::FileTest::FILE_TEST_EXISTS);
+ }
+
+ return has_cgroups;
+}
+
+
+
+static const std::pair<std::string, std::string> &
+parse_cgroup_line(const std::string& line) {
+
+ static std::unordered_map<std::string, std::pair<std::string, std::string>> line_cache;
+
+ auto it = line_cache.insert({line, {"", ""} });
+ if (it.second) { // inserted new
+ std::string::size_type cat_start, name_start;
+
+ if ((cat_start = line.find(':')) != std::string::npos
+ && (name_start = line.find(':', cat_start + 1)) != std::string::npos) {
+
+ // printf("%s %lu %lu\n", line.c_str(), cat_start, name_start);
+ auto cat = line.substr(cat_start + 1, name_start - cat_start - 1);
+ auto name = line.substr(name_start + 1);
+
+ // strip the name= prefix
+ if (cat.find("name=") == 0) {
+ cat.erase(0, 5);
+ }
+
+ if (!name.empty() && name != "/") {
+ it.first->second = {name, cat};
+ }
+ }
+ }
+
+ return it.first->second;
+}
+
+
+static const std::string&
+get_process_cgroup_string(pid_t pid) {
+
+ static std::unordered_map<std::string, std::string> file_cache{ {"", ""} };
+
+ /* read out of /proc/pid/cgroup */
+ auto path = "/proc/" + std::to_string(pid) + "/cgroup";
+ std::string text;
+
+ try {
+ text = Glib::file_get_contents(path);
+ } catch (...) {
+ return file_cache[""];
+ }
+
+ auto it = file_cache.insert({ text, "" });
+
+ if (it.second) { // inserted new
+
+ // name -> [cat...], sorted by name;
+ std::map<std::string, std::vector<std::string>> names;
+
+ std::string::size_type last = 0, eol;
+
+ // for each line in the file
+ while ((eol = text.find('\n', last)) != std::string::npos) {
+ auto line = text.substr(last, eol - last);
+ last = eol + 1;
+
+ const auto& p = parse_cgroup_line(line);
+ if (!p.first.empty()) {
+ names[p.first].push_back(p.second);
+ }
+ }
+
+
+ // name (cat1, cat2), ...
+ // sorted by name, categories
+ std::vector<std::string> groups;
+
+ for (auto& i : names) {
+ std::sort(begin(i.second), end(i.second));
+ std::string cats = procman::join(i.second, ", ");
+ groups.push_back(i.first + " (" + cats + ')');
+ }
+
+ it.first->second = procman::join(groups, ", ");
+ }
+
+ return it.first->second;
+}
+
+
+void get_process_cgroup_info(ProcInfo& info) {
+ if (not cgroups_enabled())
+ return;
+
+ const auto& cgroup_string = get_process_cgroup_string(info.pid);
+ info.cgroup_name = cgroup_string;
+}
+
diff --git a/src/cgroups.h b/src/cgroups.h
new file mode 100644
index 0000000..7ddafea
--- /dev/null
+++ b/src/cgroups.h
@@ -0,0 +1,10 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_CGROUPS_H_
+#define _GSM_CGROUPS_H_
+
+#include "application.h"
+
+void get_process_cgroup_info (ProcInfo& info);
+bool cgroups_enabled ();
+
+#endif /* _GSM_CGROUPS_H_ */
diff --git a/src/defaulttable.h b/src/defaulttable.h
new file mode 100644
index 0000000..9328212
--- /dev/null
+++ b/src/defaulttable.h
@@ -0,0 +1,53 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_DEFAULT_TABLE_H_
+#define _GSM_DEFAULT_TABLE_H_
+
+#include <string>
+#include <glibmm/refptr.h>
+#include <glibmm/regex.h>
+
+/* This file contains prettynames and icons for well-known applications, that by default has no .desktop entry */
+
+struct PrettyTableItem
+{
+ Glib::RefPtr<Glib::Regex> command;
+ std::string icon;
+
+PrettyTableItem(const std::string& a_command, const std::string& a_icon)
+: command(Glib::Regex::create("^(" + a_command + ")$")),
+ icon(a_icon)
+ { }
+};
+
+#define ITEM PrettyTableItem
+
+static const PrettyTableItem default_table[] = {
+ /* GNOME services */
+ ITEM(".*applet(-?2)?|gnome-panel", "gnome-panel"),
+ ITEM("evolution.*", "emblem-mail"),
+ ITEM("gconfd-2|dconf-service", "preferences-desktop"),
+ ITEM("metacity|gnome-shell", "gnome-window-manager"),
+ ITEM("vino.*", "gnome-remote-desktop"),
+ /* Other processes */
+ ITEM("(ba|z|tc|c|k)?sh", "utilities-terminal"),
+ ITEM("(k|sys|u)logd|logger", "internet-news-reader"),
+ ITEM("X(org)?", "display"),
+ ITEM("apache2?|httpd|lighttpd", "internet-web-browser"),
+ ITEM("atd|cron|CRON|ntpd", "date"),
+ ITEM("cupsd|lpd?", "printer"),
+ ITEM("cvsd|mtn|git|svn", "file-manager"),
+ ITEM("emacs(server|\\d+)?", "gnome-emacs"),
+ ITEM("famd|gam_server", "file-manager"),
+ ITEM("getty", "input-keyboard"),
+ ITEM("gdb|((gcc|g\\+\\+)(-.*)?)|ar|ld|make", "applications-development"),
+ ITEM("sendmail|exim\\d?", "internet-mail"),
+ ITEM("squid", "proxy"),
+ ITEM("ssh(d|-agent)", "ssh-askpass-gnome"),
+ ITEM("top|vmstat", "system-monitor"),
+ ITEM("vim?", "vim"),
+ ITEM("x?inetd", "internet-web-browser")
+};
+
+#undef ITEM
+
+#endif /* _GSM_DEFAULT_TABLE_H_ */
diff --git a/src/disks.cpp b/src/disks.cpp
new file mode 100644
index 0000000..fe79fae
--- /dev/null
+++ b/src/disks.cpp
@@ -0,0 +1,491 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <glibtop/mountlist.h>
+#include <glibtop/fsusage.h>
+#include <glib/gi18n.h>
+
+#include "disks.h"
+#include "application.h"
+#include "util.h"
+#include "settings-keys.h"
+#include "legacy/treeview.h"
+
+enum DiskColumns
+{
+ /* string columns* */
+ DISK_DEVICE,
+ DISK_DIR,
+ DISK_TYPE,
+ DISK_TOTAL,
+ DISK_FREE,
+ DISK_AVAIL,
+ /* USED has to be the last column */
+ DISK_USED,
+ // then invisible columns
+ /* PixBuf column */
+ DISK_ICON,
+ /* numeric columns */
+ DISK_USED_PERCENTAGE,
+ DISK_N_COLUMNS
+};
+
+static void
+cb_sort_changed (GtkTreeSortable *model, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ gsm_tree_view_save_state (GSM_TREE_VIEW (app->disk_list));
+}
+
+static void
+fsusage_stats(const glibtop_fsusage *buf,
+ guint64 *bused, guint64 *bfree, guint64 *bavail, guint64 *btotal,
+ gint *percentage)
+{
+ guint64 total = buf->blocks * buf->block_size;
+
+ if (!total) {
+ /* not a real device */
+ *btotal = *bfree = *bavail = *bused = 0ULL;
+ *percentage = 0;
+ } else {
+ int percent;
+ *btotal = total;
+ *bfree = buf->bfree * buf->block_size;
+ *bavail = buf->bavail * buf->block_size;
+ *bused = *btotal - *bfree;
+ /* percent = 100.0f * *bused / *btotal; */
+ percent = 100 * *bused / (*bused + *bavail);
+ *percentage = CLAMP(percent, 0, 100);
+ }
+}
+
+static const char* get_icon_for_path(const char* path)
+{
+ GVolumeMonitor *monitor;
+ GList *mounts;
+ uint i;
+ GMount *mount;
+ GIcon *icon;
+ const char* name = "";
+
+ monitor = g_volume_monitor_get ();
+ mounts = g_volume_monitor_get_mounts (monitor);
+
+ for (i = 0; i < g_list_length (mounts); i++) {
+ mount = G_MOUNT (g_list_nth_data(mounts, i));
+ if (strcmp(g_mount_get_name(mount), path))
+ continue;
+
+ icon = g_mount_get_icon (mount);
+
+ if (!icon)
+ continue;
+ name = g_icon_to_string (icon);
+ g_object_unref (icon);
+ }
+
+ g_list_free_full (mounts, g_object_unref);
+ return name;
+
+}
+
+static GdkPixbuf*
+get_icon_for_device(const char *mountpoint)
+{
+ const char* icon_name = get_icon_for_path(mountpoint);
+ if (!strcmp(icon_name, ""))
+ // FIXME: defaults to a safe value
+ icon_name = "drive-harddisk"; // get_icon_for_path("/");
+ return gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), icon_name, 24, GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
+}
+
+
+static gboolean
+find_disk_in_model(GtkTreeModel *model, const char *mountpoint,
+ GtkTreeIter *result)
+{
+ GtkTreeIter iter;
+ gboolean found = FALSE;
+
+ if (gtk_tree_model_get_iter_first(model, &iter)) {
+ do {
+ char *dir;
+
+ gtk_tree_model_get(model, &iter,
+ DISK_DIR, &dir,
+ -1);
+
+ if (dir && !strcmp(dir, mountpoint)) {
+ *result = iter;
+ found = TRUE;
+ }
+
+ g_free(dir);
+
+ } while (!found && gtk_tree_model_iter_next(model, &iter));
+ }
+
+ return found;
+}
+
+
+
+static void
+remove_old_disks(GtkTreeModel *model, const glibtop_mountentry *entries, guint n)
+{
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_get_iter_first(model, &iter))
+ return;
+
+ while (true) {
+ char *dir;
+ guint i;
+ gboolean found = FALSE;
+
+ gtk_tree_model_get(model, &iter,
+ DISK_DIR, &dir,
+ -1);
+
+ for (i = 0; i != n; ++i) {
+ if (!strcmp(dir, entries[i].mountdir)) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ g_free(dir);
+
+ if (!found) {
+ if (!gtk_list_store_remove(GTK_LIST_STORE(model), &iter))
+ break;
+ else
+ continue;
+ }
+
+ if (!gtk_tree_model_iter_next(model, &iter))
+ break;
+ }
+}
+
+
+
+static void
+add_disk(GtkListStore *list, const glibtop_mountentry *entry, bool show_all_fs)
+{
+ GdkPixbuf* pixbuf;
+ GtkTreeIter iter;
+ glibtop_fsusage usage;
+ guint64 bused, bfree, bavail, btotal;
+ gint percentage;
+
+ glibtop_get_fsusage(&usage, entry->mountdir);
+
+ if (not show_all_fs and usage.blocks == 0) {
+ if (find_disk_in_model(GTK_TREE_MODEL(list), entry->mountdir, &iter))
+ gtk_list_store_remove(list, &iter);
+ return;
+ }
+
+ fsusage_stats(&usage, &bused, &bfree, &bavail, &btotal, &percentage);
+ pixbuf = get_icon_for_device(entry->mountdir);
+
+ /* if we can find a row with the same mountpoint, we get it but we
+ still need to update all the fields.
+ This makes selection persistent.
+ */
+ if (!find_disk_in_model(GTK_TREE_MODEL(list), entry->mountdir, &iter))
+ gtk_list_store_append(list, &iter);
+
+ gtk_list_store_set(list, &iter,
+ DISK_ICON, pixbuf,
+ DISK_DEVICE, entry->devname,
+ DISK_DIR, entry->mountdir,
+ DISK_TYPE, entry->type,
+ DISK_USED_PERCENTAGE, percentage,
+ DISK_TOTAL, btotal,
+ DISK_FREE, bfree,
+ DISK_AVAIL, bavail,
+ DISK_USED, bused,
+ -1);
+}
+
+static void
+mount_changed (GVolumeMonitor *monitor, GMount *mount, GsmApplication *app)
+{
+ disks_update(app);
+}
+
+static gboolean
+cb_timeout (gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ disks_update (app);
+
+ return G_SOURCE_CONTINUE;
+}
+
+void
+disks_update(GsmApplication *app)
+{
+ GtkListStore *list;
+ glibtop_mountentry * entries;
+ glibtop_mountlist mountlist;
+ guint i;
+ gboolean show_all_fs;
+
+ list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(app->disk_list)));
+ show_all_fs = app->settings->get_boolean (GSM_SETTING_SHOW_ALL_FS);
+ entries = glibtop_get_mountlist (&mountlist, show_all_fs);
+
+ remove_old_disks(GTK_TREE_MODEL(list), entries, mountlist.number);
+
+ for (i = 0; i < mountlist.number; i++)
+ add_disk(list, &entries[i], show_all_fs);
+
+ g_free(entries);
+}
+
+static void
+init_volume_monitor (GsmApplication *app)
+{
+ GVolumeMonitor *monitor = g_volume_monitor_get ();
+
+ g_signal_connect (monitor, "mount-added", G_CALLBACK (mount_changed), app);
+ g_signal_connect (monitor, "mount-changed", G_CALLBACK (mount_changed), app);
+ g_signal_connect (monitor, "mount-removed", G_CALLBACK (mount_changed), app);
+}
+
+void
+disks_freeze (GsmApplication *app)
+{
+ if (app->disk_timeout) {
+ g_source_remove (app->disk_timeout);
+ app->disk_timeout = 0;
+ }
+}
+
+void
+disks_thaw (GsmApplication *app)
+{
+ if (app->disk_timeout)
+ return;
+
+ app->disk_timeout = g_timeout_add (app->config.disks_update_interval,
+ cb_timeout,
+ app);
+}
+
+void
+disks_reset_timeout (GsmApplication *app)
+{
+ disks_freeze (app);
+ disks_thaw (app);
+}
+
+static void
+cb_disk_columns_changed(GtkTreeView *treeview, gpointer data)
+{
+ gsm_tree_view_save_state (GSM_TREE_VIEW (treeview));
+}
+
+
+static void open_dir(GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ char *dir, *url;
+
+ model = gtk_tree_view_get_model(tree_view);
+
+ if (!gtk_tree_model_get_iter(model, &iter, path)) {
+ char *p;
+ p = gtk_tree_path_to_string(path);
+ g_warning("Cannot get iter for path '%s'\n", p);
+ g_free(p);
+ return;
+ }
+
+ gtk_tree_model_get(model, &iter, DISK_DIR, &dir, -1);
+
+ url = g_strdup_printf("file://%s", dir);
+
+ GError* error = 0;
+ if (!g_app_info_launch_default_for_uri(url, NULL, &error)) {
+ g_warning("Cannot open '%s' : %s\n", url, error->message);
+ g_error_free(error);
+ }
+
+ g_free(url);
+ g_free(dir);
+}
+
+static void
+cb_disk_list_destroying (GtkWidget *self, gpointer data)
+{
+ g_signal_handlers_disconnect_by_func(self, (gpointer) cb_disk_columns_changed, data);
+
+ g_signal_handlers_disconnect_by_func (gtk_tree_view_get_model (GTK_TREE_VIEW(self)),
+ (gpointer) cb_sort_changed,
+ data);
+}
+
+
+void
+create_disk_view(GsmApplication *app, GtkBuilder *builder)
+{
+ GtkScrolledWindow *scrolled;
+ GsmTreeView *disk_tree;
+ GtkListStore *model;
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *cell;
+ PangoAttrList *attrs = NULL;
+ guint i;
+
+ init_volume_monitor (app);
+ const gchar * const titles[] = {
+ N_("Device"),
+ N_("Directory"),
+ N_("Type"),
+ N_("Total"),
+ N_("Free"),
+ N_("Available"),
+ N_("Used")
+ };
+
+ scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "disks_scrolled"));
+
+ model = gtk_list_store_new(DISK_N_COLUMNS, /* n columns */
+ G_TYPE_STRING, /* DISK_DEVICE */
+ G_TYPE_STRING, /* DISK_DIR */
+ G_TYPE_STRING, /* DISK_TYPE */
+ G_TYPE_UINT64, /* DISK_TOTAL */
+ G_TYPE_UINT64, /* DISK_FREE */
+ G_TYPE_UINT64, /* DISK_AVAIL */
+ G_TYPE_UINT64, /* DISK_USED */
+ GDK_TYPE_PIXBUF, /* DISK_ICON */
+ G_TYPE_INT /* DISK_USED_PERCENTAGE */
+ );
+ disk_tree = gsm_tree_view_new (g_settings_get_child (app->settings->gobj(), GSM_SETTINGS_CHILD_DISKS), TRUE);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (disk_tree), GTK_TREE_MODEL (model));
+
+ g_signal_connect(G_OBJECT(disk_tree), "row-activated", G_CALLBACK(open_dir), NULL);
+ app->disk_list = disk_tree;
+ gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET (disk_tree));
+ g_object_unref(G_OBJECT(model));
+
+ /* icon + device */
+
+ col = gtk_tree_view_column_new();
+ cell = gtk_cell_renderer_pixbuf_new();
+
+ gtk_tree_view_column_pack_start(col, cell, FALSE);
+ gtk_tree_view_column_set_min_width(col, 30);
+ gtk_tree_view_column_set_attributes(col, cell, "pixbuf", DISK_ICON,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(col, cell, FALSE);
+ gtk_tree_view_column_set_attributes(col, cell, "text", DISK_DEVICE,
+ NULL);
+ gtk_tree_view_column_set_title(col, _(titles[DISK_DEVICE]));
+ gtk_tree_view_column_set_sort_column_id(col, DISK_DEVICE);
+ gtk_tree_view_column_set_reorderable(col, TRUE);
+ gtk_tree_view_column_set_resizable(col, TRUE);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);
+
+
+ /* sizes - used */
+
+ for (i = DISK_DIR; i <= DISK_AVAIL; i++) {
+ cell = gtk_cell_renderer_text_new();
+ col = gtk_tree_view_column_new();
+ gtk_tree_view_column_pack_start(col, cell, TRUE);
+ gtk_tree_view_column_set_title(col, _(titles[i]));
+ gtk_tree_view_column_set_resizable(col, TRUE);
+ gtk_tree_view_column_set_sort_column_id(col, i);
+ gtk_tree_view_column_set_reorderable(col, TRUE);
+ gtk_tree_view_column_set_min_width(col, i == DISK_TYPE ? 40 : 72);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);
+ switch (i) {
+ case DISK_TOTAL:
+ case DISK_FREE:
+ case DISK_AVAIL:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::size_si_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+
+ attrs = make_tnum_attr_list ();
+ g_object_set (cell,
+ "attributes", attrs,
+ "xalign", 1.0f,
+ NULL);
+ g_clear_pointer (&attrs, pango_attr_list_unref);
+
+ break;
+
+ default:
+ gtk_tree_view_column_set_attributes(col, cell,
+ "text", i,
+ NULL);
+ break;
+ }
+ }
+
+ /* used + percentage */
+
+ col = gtk_tree_view_column_new();
+ cell = gtk_cell_renderer_text_new();
+
+ attrs = make_tnum_attr_list ();
+ g_object_set (cell,
+ "attributes", attrs,
+ "xalign", 1.0f,
+ NULL);
+ g_clear_pointer (&attrs, pango_attr_list_unref);
+
+ gtk_tree_view_column_set_min_width(col, 72);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_pack_start(col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::size_si_cell_data_func,
+ GUINT_TO_POINTER(DISK_USED),
+ NULL);
+ gtk_tree_view_column_set_title(col, _(titles[DISK_USED]));
+
+ cell = gtk_cell_renderer_progress_new();
+ gtk_cell_renderer_set_padding(cell, 4.0f, 4.0f);
+ gtk_tree_view_column_pack_start(col, cell, TRUE);
+ gtk_tree_view_column_set_attributes(col, cell, "value",
+ DISK_USED_PERCENTAGE, NULL);
+ gtk_tree_view_column_set_resizable(col, TRUE);
+ gtk_tree_view_column_set_sort_column_id(col, DISK_USED);
+ gtk_tree_view_column_set_reorderable(col, TRUE);
+ gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);
+
+ /* numeric sort */
+
+ gsm_tree_view_load_state (GSM_TREE_VIEW (disk_tree));
+ g_signal_connect (G_OBJECT(disk_tree), "destroy",
+ G_CALLBACK(cb_disk_list_destroying),
+ app);
+
+ g_signal_connect (G_OBJECT(disk_tree), "columns-changed",
+ G_CALLBACK(cb_disk_columns_changed), app);
+
+ g_signal_connect (G_OBJECT (model), "sort-column-changed",
+ G_CALLBACK (cb_sort_changed), app);
+
+ app->settings->signal_changed(GSM_SETTING_SHOW_ALL_FS).connect ([app](const Glib::ustring&) { disks_update (app); disks_reset_timeout (app); });
+
+ gtk_widget_show (GTK_WIDGET (disk_tree));
+}
diff --git a/src/disks.h b/src/disks.h
new file mode 100644
index 0000000..36feb1c
--- /dev/null
+++ b/src/disks.h
@@ -0,0 +1,14 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_DISKS_H_
+#define _GSM_DISKS_H_
+
+#include "application.h"
+
+void create_disk_view(GsmApplication *app, GtkBuilder *builder);
+
+void disks_update (GsmApplication *app);
+void disks_freeze (GsmApplication *app);
+void disks_thaw (GsmApplication *app);
+void disks_reset_timeout (GsmApplication *app);
+
+#endif /* _GSM_DISKS_H_ */
diff --git a/src/gsm.gresource.xml b/src/gsm.gresource.xml
new file mode 100644
index 0000000..a8abf39
--- /dev/null
+++ b/src/gsm.gresource.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/gnome-system-monitor">
+ <file preprocess="xml-stripblanks">data/interface.ui</file>
+ <file preprocess="xml-stripblanks">data/lsof.ui</file>
+ <file preprocess="xml-stripblanks">data/openfiles.ui</file>
+ <file preprocess="xml-stripblanks">data/preferences.ui</file>
+ <file preprocess="xml-stripblanks">data/renice.ui</file>
+ <file preprocess="xml-stripblanks">data/menus.ui</file>
+ <file preprocess="xml-stripblanks">pixmaps/download.svg</file>
+ <file preprocess="xml-stripblanks">pixmaps/upload.svg</file>
+ <file preprocess="xml-stripblanks" alias="gtk/help-overlay.ui">data/help-overlay.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/gsm_gksu.cpp b/src/gsm_gksu.cpp
new file mode 100644
index 0000000..d2146dd
--- /dev/null
+++ b/src/gsm_gksu.cpp
@@ -0,0 +1,55 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include "application.h"
+#include "gsm_gksu.h"
+#include "util.h"
+
+static gboolean (*gksu_run)(const char *, GError **);
+
+
+static void load_gksu(void)
+{
+ static gboolean init;
+
+ if (init)
+ return;
+
+ init = TRUE;
+
+ load_symbols("libgksu2.so",
+ "gksu_run", &gksu_run,
+ NULL);
+}
+
+
+
+
+
+gboolean gsm_gksu_create_root_password_dialog(const char *command)
+{
+ GError *e = NULL;
+
+ /* Returns FALSE or TRUE on success, depends on version ... */
+ gksu_run(command, &e);
+
+ if (e) {
+ g_critical("Could not run gksu_run(\"%s\") : %s\n",
+ command, e->message);
+ g_error_free(e);
+ return FALSE;
+ }
+
+ g_message("gksu_run did fine\n");
+ return TRUE;
+}
+
+
+
+gboolean
+procman_has_gksu(void)
+{
+ load_gksu();
+ return gksu_run != NULL;
+}
+
diff --git a/src/gsm_gksu.h b/src/gsm_gksu.h
new file mode 100644
index 0000000..d7c4787
--- /dev/null
+++ b/src/gsm_gksu.h
@@ -0,0 +1,13 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_GSM_GKSU_H_
+#define _GSM_GSM_GKSU_H_
+
+#include <glib.h>
+
+gboolean
+gsm_gksu_create_root_password_dialog(const char * command);
+
+gboolean
+procman_has_gksu(void) G_GNUC_CONST;
+
+#endif /* _GSM_GSM_GKSU_H_ */
diff --git a/src/gsm_gnomesu.cpp b/src/gsm_gnomesu.cpp
new file mode 100644
index 0000000..31b8645
--- /dev/null
+++ b/src/gsm_gnomesu.cpp
@@ -0,0 +1,41 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glib.h>
+
+#include "application.h"
+#include "gsm_gnomesu.h"
+#include "util.h"
+
+gboolean (*gnomesu_exec)(const char *commandline);
+
+
+static void
+load_gnomesu(void)
+{
+ static gboolean init;
+
+ if (init)
+ return;
+
+ init = TRUE;
+
+ load_symbols("libgnomesu.so.0",
+ "gnomesu_exec", &gnomesu_exec,
+ NULL);
+}
+
+
+gboolean
+gsm_gnomesu_create_root_password_dialog(const char *command)
+{
+ return gnomesu_exec(command);
+}
+
+
+gboolean
+procman_has_gnomesu(void)
+{
+ load_gnomesu();
+ return gnomesu_exec != NULL;
+}
diff --git a/src/gsm_gnomesu.h b/src/gsm_gnomesu.h
new file mode 100644
index 0000000..323059b
--- /dev/null
+++ b/src/gsm_gnomesu.h
@@ -0,0 +1,13 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_GSM_GNOMESU_H_
+#define _GSM_GSM_GNOMESU_H_
+
+#include <glib.h>
+
+gboolean
+gsm_gnomesu_create_root_password_dialog(const char * message);
+
+gboolean
+procman_has_gnomesu(void) G_GNUC_CONST;
+
+#endif /* _GSM_GSM_GNOMESU_H_ */
diff --git a/src/gsm_pkexec.cpp b/src/gsm_pkexec.cpp
new file mode 100644
index 0000000..868969b
--- /dev/null
+++ b/src/gsm_pkexec.cpp
@@ -0,0 +1,38 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include "application.h"
+#include "gsm_pkexec.h"
+#include "util.h"
+
+gboolean gsm_pkexec_create_root_password_dialog(const char *command)
+{
+ gboolean ret = FALSE;
+ gint *exit_status = NULL;
+ GError *error = NULL;
+ gchar *command_line = g_strdup_printf("pkexec --disable-internal-agent %s/gsm-%s",
+ GSM_LIBEXEC_DIR, command);
+ if (!g_spawn_command_line_sync(command_line, NULL, NULL, exit_status, &error)) {
+ g_critical("Could not run pkexec(\"%s\") : %s\n",
+ command, error->message);
+ g_error_free(error);
+ }
+ else
+ {
+ g_debug("pkexec did fine\n");
+ ret = TRUE;
+ }
+
+ g_free (command_line);
+
+ return ret;
+}
+
+
+
+gboolean
+procman_has_pkexec(void)
+{
+ return g_file_test("/usr/bin/pkexec", G_FILE_TEST_EXISTS);
+}
+
diff --git a/src/gsm_pkexec.h b/src/gsm_pkexec.h
new file mode 100644
index 0000000..0aec1a7
--- /dev/null
+++ b/src/gsm_pkexec.h
@@ -0,0 +1,13 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_GSM_PKEXEC_H_
+#define _GSM_GSM_PKEXEC_H_
+
+#include <glib.h>
+
+gboolean
+gsm_pkexec_create_root_password_dialog(const char *command);
+
+gboolean
+procman_has_pkexec(void) G_GNUC_CONST;
+
+#endif /* _GSM_PKEXEC_H_ */
diff --git a/src/interface.cpp b/src/interface.cpp
new file mode 100644
index 0000000..5b8ac6b
--- /dev/null
+++ b/src/interface.cpp
@@ -0,0 +1,887 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman - main window
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <handy.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <gdk/gdkkeysyms.h>
+#include <math.h>
+
+#include "interface.h"
+#include "proctable.h"
+#include "procactions.h"
+#include "procdialogs.h"
+#include "setaffinity.h"
+#include "memmaps.h"
+#include "openfiles.h"
+#include "procproperties.h"
+#include "load-graph.h"
+#include "util.h"
+#include "disks.h"
+#include "settings-keys.h"
+#include "legacy/gsm_color_button.h"
+
+static const char* LOAD_GRAPH_CSS = "\
+.loadgraph {\
+ background: linear-gradient(to bottom,\
+ @theme_bg_color,\
+ @theme_base_color\
+ );\
+ color: mix (@theme_fg_color, @theme_bg_color, 0.5);\
+}\
+";
+
+static gboolean
+cb_window_key_press_event (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ const char *current_page = gtk_stack_get_visible_child_name (GTK_STACK (GsmApplication::get()->stack));
+
+ if (strcmp (current_page, "processes") == 0)
+ return gtk_search_bar_handle_event (GTK_SEARCH_BAR (user_data), event);
+
+ return FALSE;
+}
+
+static void
+search_text_changed (GtkEditable *entry, gpointer data)
+{
+ GsmApplication * const app = static_cast<GsmApplication *>(data);
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (gtk_tree_model_sort_get_model (
+ GTK_TREE_MODEL_SORT (gtk_tree_view_get_model(
+ GTK_TREE_VIEW (app->tree))))));
+}
+
+static void
+set_affinity_visiblity (GtkWidget *widget, gpointer user_data)
+{
+#ifndef __linux__
+ GtkMenuItem *item = GTK_MENU_ITEM (widget);
+ const gchar *name = gtk_menu_item_get_label (item);
+
+ if (strcmp (name, "Set _Affinity") == 0) {
+ gtk_widget_set_visible (widget, false);
+ }
+#endif
+}
+
+static void
+create_proc_view(GsmApplication *app, GtkBuilder * builder)
+{
+ GsmTreeView *proctree;
+ GtkScrolledWindow *scrolled;
+
+ proctree = proctable_new (app);
+ scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "processes_scrolled"));
+
+ gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (proctree));
+
+ app->proc_actionbar_revealer = GTK_REVEALER (gtk_builder_get_object (builder, "proc_actionbar_revealer"));
+
+ /* create popup_menu for the processes tab */
+ GMenuModel *menu_model = G_MENU_MODEL (gtk_builder_get_object (builder, "process-popup-menu"));
+ app->popup_menu = GTK_MENU (gtk_menu_new_from_model (menu_model));
+ gtk_menu_attach_to_widget (app->popup_menu, GTK_WIDGET (app->main_window), NULL);
+
+ gtk_container_foreach (GTK_CONTAINER (app->popup_menu), set_affinity_visiblity, NULL);
+
+ app->search_bar = GTK_SEARCH_BAR (gtk_builder_get_object (builder, "proc_searchbar"));
+ app->search_entry = GTK_SEARCH_ENTRY (gtk_builder_get_object (builder, "proc_searchentry"));
+
+ gtk_search_bar_connect_entry (app->search_bar, GTK_ENTRY (app->search_entry));
+ g_signal_connect (app->main_window, "key-press-event",
+ G_CALLBACK (cb_window_key_press_event), app->search_bar);
+
+ g_signal_connect (app->search_entry, "changed", G_CALLBACK (search_text_changed), app);
+
+ g_object_bind_property (app->search_bar, "search-mode-enabled", app->search_button, "active", (GBindingFlags)(G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE));
+}
+
+static void
+cb_cpu_color_changed (GsmColorButton *cp, gpointer data)
+{
+ guint cpu_i = GPOINTER_TO_UINT (data);
+ auto settings = Gio::Settings::create (GSM_GSETTINGS_SCHEMA);
+
+ /* Get current values */
+ GVariant *cpu_colors_var = g_settings_get_value (settings->gobj(), GSM_SETTING_CPU_COLORS);
+ gsize children_n = g_variant_n_children(cpu_colors_var);
+
+ /* Create builder to construct new setting with updated value for cpu i */
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
+
+ for (guint i = 0; i < children_n; i++) {
+ if(cpu_i == i) {
+ gchar *color;
+ GdkRGBA button_color;
+ gsm_color_button_get_color(cp, &button_color);
+ color = gdk_rgba_to_string (&button_color);
+ g_variant_builder_add(&builder, "(us)", i, color);
+ g_free (color);
+ } else {
+ g_variant_builder_add_value(&builder,
+ g_variant_get_child_value(cpu_colors_var, i));
+ }
+ }
+
+ /* Just set the value and let the changed::cpu-colors signal callback do the rest. */
+ settings->set_value (GSM_SETTING_CPU_COLORS, Glib::wrap (g_variant_builder_end(&builder)));
+}
+
+static void change_settings_color(Gio::Settings& settings, const char *key,
+ GsmColorButton *cp)
+{
+ GdkRGBA c;
+ char *color;
+
+ gsm_color_button_get_color(cp, &c);
+ color = gdk_rgba_to_string (&c);
+ settings.set_string (key, color);
+ g_free (color);
+}
+
+static void
+cb_mem_color_changed (GsmColorButton *cp, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ change_settings_color (*app->settings.operator->(), GSM_SETTING_MEM_COLOR, cp);
+}
+
+
+static void
+cb_swap_color_changed (GsmColorButton *cp, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ change_settings_color (*app->settings.operator->(), GSM_SETTING_SWAP_COLOR, cp);
+}
+
+static void
+cb_net_in_color_changed (GsmColorButton *cp, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ change_settings_color (*app->settings.operator->(), GSM_SETTING_NET_IN_COLOR, cp);
+}
+
+static void
+cb_net_out_color_changed (GsmColorButton *cp, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ change_settings_color(*app->settings.operator->(), GSM_SETTING_NET_OUT_COLOR, cp);
+}
+
+static void
+create_sys_view (GsmApplication *app, GtkBuilder * builder)
+{
+ GtkBox *cpu_graph_box, *mem_graph_box, *net_graph_box;
+ GtkExpander *cpu_expander, *mem_expander, *net_expander;
+ GtkLabel *label,*cpu_label;
+ GtkGrid *table;
+ GsmColorButton *color_picker;
+ GtkCssProvider *provider;
+
+ LoadGraph *cpu_graph, *mem_graph, *net_graph;
+
+ gint i;
+ gchar *title_text;
+ gchar *label_text;
+ gchar *title_template;
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (provider, LOAD_GRAPH_CSS, -1, NULL);
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ // Translators: color picker title, %s is CPU, Memory, Swap, Receiving, Sending
+ title_template = g_strdup(_("Pick a Color for “%s”"));
+
+ /* The CPU BOX */
+
+ cpu_graph_box = GTK_BOX (gtk_builder_get_object (builder, "cpu_graph_box"));
+ cpu_expander = GTK_EXPANDER (gtk_builder_get_object (builder, "cpu_expander"));
+ g_object_bind_property (cpu_expander, "expanded", cpu_expander, "vexpand", G_BINDING_DEFAULT);
+
+ cpu_graph = new LoadGraph(LOAD_GRAPH_CPU);
+ gtk_widget_set_size_request (GTK_WIDGET(load_graph_get_widget(cpu_graph)), -1, 70);
+ gtk_box_pack_start (cpu_graph_box,
+ GTK_WIDGET (load_graph_get_widget(cpu_graph)),
+ TRUE,
+ TRUE,
+ 0);
+
+ GtkGrid* cpu_table = GTK_GRID (gtk_builder_get_object (builder, "cpu_table"));
+ gint cols = 4;
+ for (i=0;i<app->config.num_cpus; i++) {
+ GtkBox *temp_hbox;
+
+ temp_hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
+ gtk_widget_show (GTK_WIDGET (temp_hbox));
+ if (i < cols) {
+ gtk_grid_insert_column(cpu_table, i%cols);
+ }
+ if ((i+1)%cols ==cols) {
+ gtk_grid_insert_row(cpu_table, (i+1)/cols);
+ }
+ gtk_grid_attach(cpu_table, GTK_WIDGET (temp_hbox), i%cols, i/cols, 1, 1);
+ color_picker = gsm_color_button_new (&cpu_graph->colors.at(i), GSMCP_TYPE_CPU);
+ g_signal_connect (G_OBJECT (color_picker), "color-set",
+ G_CALLBACK (cb_cpu_color_changed), GINT_TO_POINTER (i));
+ gtk_box_pack_start (temp_hbox, GTK_WIDGET (color_picker), FALSE, TRUE, 0);
+ gtk_widget_set_size_request(GTK_WIDGET(color_picker), 32, -1);
+ if(app->config.num_cpus == 1) {
+ label_text = g_strdup (_("CPU"));
+ } else {
+ label_text = g_strdup_printf (_("CPU%d"), i+1);
+ }
+ title_text = g_strdup_printf(title_template, label_text);
+ label = GTK_LABEL (gtk_label_new (label_text));
+ gtk_label_set_xalign(label, 0.0);
+ if(app->config.num_cpus >=10) {
+ gtk_label_set_width_chars(label, log10(app->config.num_cpus)+1+4);
+ }
+ gsm_color_button_set_title(color_picker, title_text);
+ g_free(title_text);
+ gtk_box_pack_start (temp_hbox, GTK_WIDGET (label), FALSE, FALSE, 6);
+ gtk_widget_show (GTK_WIDGET (label));
+ g_free (label_text);
+
+ cpu_label = make_tnum_label ();
+
+ /* Reserve some space to avoid the layout changing with the values. */
+ gtk_label_set_width_chars(cpu_label, 6);
+ gtk_label_set_xalign(cpu_label, 1.0);
+ gtk_widget_set_valign (GTK_WIDGET (cpu_label), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (cpu_label), GTK_ALIGN_START);
+ gtk_box_pack_start (temp_hbox, GTK_WIDGET (cpu_label), FALSE, FALSE, 0);
+ gtk_widget_show (GTK_WIDGET (cpu_label));
+ load_graph_get_labels(cpu_graph)->cpu[i] = cpu_label;
+
+ }
+
+ app->cpu_graph = cpu_graph;
+
+ /** The memory box */
+
+ mem_graph_box = GTK_BOX (gtk_builder_get_object (builder, "mem_graph_box"));
+ mem_expander = GTK_EXPANDER (gtk_builder_get_object (builder, "mem_expander"));
+ g_object_bind_property (mem_expander, "expanded", mem_expander, "vexpand", G_BINDING_DEFAULT);
+
+ mem_graph = new LoadGraph(LOAD_GRAPH_MEM);
+ gtk_widget_set_size_request (GTK_WIDGET(load_graph_get_widget(mem_graph)), -1, 70);
+ gtk_box_pack_start (mem_graph_box,
+ GTK_WIDGET (load_graph_get_widget(mem_graph)),
+ TRUE,
+ TRUE,
+ 0);
+
+ table = GTK_GRID (gtk_builder_get_object (builder, "mem_table"));
+
+ color_picker = load_graph_get_mem_color_picker(mem_graph);
+ g_signal_connect (G_OBJECT (color_picker), "color-set",
+ G_CALLBACK (cb_mem_color_changed), app);
+ title_text = g_strdup_printf(title_template, _("Memory"));
+ gsm_color_button_set_title(color_picker, title_text);
+ g_free(title_text);
+
+ label = GTK_LABEL (gtk_builder_get_object(builder, "memory_label"));
+
+ gtk_grid_attach_next_to (table, GTK_WIDGET (color_picker), GTK_WIDGET (label), GTK_POS_LEFT, 1, 3);
+ gtk_grid_attach_next_to (table, GTK_WIDGET (load_graph_get_labels(mem_graph)->memory), GTK_WIDGET (label), GTK_POS_BOTTOM, 1, 2);
+
+ color_picker = load_graph_get_swap_color_picker(mem_graph);
+ g_signal_connect (G_OBJECT (color_picker), "color-set",
+ G_CALLBACK (cb_swap_color_changed), app);
+ title_text = g_strdup_printf(title_template, _("Swap"));
+ gsm_color_button_set_title(GSM_COLOR_BUTTON(color_picker), title_text);
+ g_free(title_text);
+
+ label = GTK_LABEL (gtk_builder_get_object(builder, "swap_label"));
+
+ gtk_grid_attach_next_to (table, GTK_WIDGET (color_picker), GTK_WIDGET (label), GTK_POS_LEFT, 1, 3);
+ gtk_grid_attach_next_to (table, GTK_WIDGET (load_graph_get_labels(mem_graph)->swap), GTK_WIDGET (label), GTK_POS_BOTTOM, 1, 2);
+
+ app->mem_graph = mem_graph;
+
+ /* The net box */
+
+ net_graph_box = GTK_BOX (gtk_builder_get_object (builder, "net_graph_box"));
+ net_expander = GTK_EXPANDER (gtk_builder_get_object (builder, "net_expander"));
+ g_object_bind_property (net_expander, "expanded", net_expander, "vexpand", G_BINDING_DEFAULT);
+
+ net_graph = new LoadGraph(LOAD_GRAPH_NET);
+ gtk_widget_set_size_request (GTK_WIDGET(load_graph_get_widget(net_graph)), -1, 70);
+ gtk_box_pack_start (net_graph_box,
+ GTK_WIDGET (load_graph_get_widget(net_graph)),
+ TRUE,
+ TRUE,
+ 0);
+
+ table = GTK_GRID (gtk_builder_get_object (builder, "net_table"));
+
+ color_picker = gsm_color_button_new (
+ &net_graph->colors.at(0), GSMCP_TYPE_NETWORK_IN);
+ gtk_widget_set_valign (GTK_WIDGET(color_picker), GTK_ALIGN_CENTER);
+ g_signal_connect (G_OBJECT (color_picker), "color-set",
+ G_CALLBACK (cb_net_in_color_changed), app);
+ title_text = g_strdup_printf(title_template, _("Receiving"));
+ gsm_color_button_set_title(color_picker, title_text);
+ g_free(title_text);
+
+ label = GTK_LABEL (gtk_builder_get_object(builder, "receiving_label"));
+ gtk_grid_attach_next_to (table, GTK_WIDGET (color_picker), GTK_WIDGET (label), GTK_POS_LEFT, 1, 2);
+ gtk_grid_attach_next_to (table, GTK_WIDGET (load_graph_get_labels(net_graph)->net_in), GTK_WIDGET (label), GTK_POS_RIGHT, 1, 1);
+ label = GTK_LABEL (gtk_builder_get_object(builder, "total_received_label"));
+ gtk_grid_attach_next_to (table, GTK_WIDGET (load_graph_get_labels(net_graph)->net_in_total), GTK_WIDGET (label), GTK_POS_RIGHT, 1, 1);
+
+ color_picker = gsm_color_button_new (
+ &net_graph->colors.at(1), GSMCP_TYPE_NETWORK_OUT);
+ gtk_widget_set_valign (GTK_WIDGET(color_picker), GTK_ALIGN_CENTER);
+ gtk_widget_set_hexpand (GTK_WIDGET(color_picker), true);
+ gtk_widget_set_halign (GTK_WIDGET(color_picker), GTK_ALIGN_END);
+
+ g_signal_connect (G_OBJECT (color_picker), "color-set",
+ G_CALLBACK (cb_net_out_color_changed), app);
+ title_text = g_strdup_printf(title_template, _("Sending"));
+ gsm_color_button_set_title(color_picker, title_text);
+ g_free(title_text);
+
+ label = GTK_LABEL (gtk_builder_get_object(builder, "sending_label"));
+ gtk_grid_attach_next_to (table, GTK_WIDGET (color_picker), GTK_WIDGET (label), GTK_POS_LEFT, 1, 2);
+ gtk_grid_attach_next_to (table, GTK_WIDGET (load_graph_get_labels(net_graph)->net_out), GTK_WIDGET (label), GTK_POS_RIGHT, 1, 1);
+ label = GTK_LABEL (gtk_builder_get_object(builder, "total_sent_label"));
+ gtk_grid_attach_next_to (table, GTK_WIDGET (load_graph_get_labels(net_graph)->net_out_total), GTK_WIDGET (label), GTK_POS_RIGHT, 1, 1);
+ gtk_widget_set_hexpand (GTK_WIDGET(load_graph_get_labels(net_graph)->net_out_total), true);
+ gtk_widget_set_halign (GTK_WIDGET(load_graph_get_labels(net_graph)->net_out_total), GTK_ALIGN_START);
+
+ gtk_widget_set_hexpand (GTK_WIDGET(load_graph_get_labels(net_graph)->net_out), true);
+ gtk_widget_set_halign (GTK_WIDGET(load_graph_get_labels(net_graph)->net_out), GTK_ALIGN_START);
+
+
+ app->net_graph = net_graph;
+ g_free (title_template);
+
+}
+
+static void
+on_activate_about (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ const gchar * const authors[] = {
+ "Kevin Vandersloot",
+ "Erik Johnsson",
+ "Jorgen Scheibengruber",
+ "Benoît Dejean",
+ "Paolo Borelli",
+ "Karl Lattimer",
+ "Chris Kühl",
+ "Robert Roth",
+ "Stefano Facchini",
+ "Jacob Barkdull",
+ NULL
+ };
+
+ const gchar * const documenters[] = {
+ "Bill Day",
+ "Sun Microsystems",
+ NULL
+ };
+
+ const gchar * const artists[] = {
+ "Baptiste Mille-Mathias",
+ NULL
+ };
+
+ gtk_show_about_dialog (
+ GTK_WINDOW (app->main_window),
+ "name", _("System Monitor"),
+ "comments", _("View current processes and monitor "
+ "system state"),
+ "version", VERSION,
+ "website", "https://wiki.gnome.org/Apps/SystemMonitor",
+ "copyright", "Copyright \xc2\xa9 2001-2004 Kevin Vandersloot\n"
+ "Copyright \xc2\xa9 2005-2007 Benoît Dejean\n"
+ "Copyright \xc2\xa9 2011 Chris Kühl",
+ "logo-icon-name", "org.gnome.SystemMonitor",
+ "authors", authors,
+ "artists", artists,
+ "documenters", documenters,
+ "translator-credits", _("translator-credits"),
+ "license-type", GTK_LICENSE_GPL_2_0,
+ NULL
+ );
+}
+
+static void
+on_activate_keyboard_shortcuts (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ gtk_widget_show (GTK_WIDGET (gtk_application_window_get_help_overlay (GTK_APPLICATION_WINDOW (app->main_window))));
+}
+
+static void
+on_activate_refresh (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ proctable_update (app);
+}
+
+static void
+kill_process_with_confirmation (GsmApplication *app, int signal) {
+ gboolean kill_dialog = app->settings->get_boolean(GSM_SETTING_SHOW_KILL_DIALOG);
+
+ if (kill_dialog)
+ procdialog_create_kill_dialog (app, signal);
+ else
+ kill_process (app, signal);
+}
+
+static void
+on_activate_send_signal (GSimpleAction *, GVariant *parameter, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ /* no confirmation */
+ gint32 signal = g_variant_get_int32(parameter);
+ switch (signal) {
+ case SIGCONT:
+ kill_process (app, signal);
+ break;
+ case SIGSTOP:
+ case SIGTERM:
+ case SIGKILL:
+ kill_process_with_confirmation (app, signal);
+ break;
+ }
+}
+
+static void
+on_activate_set_affinity (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ create_set_affinity_dialog (app);
+}
+
+static void
+on_activate_memory_maps (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ create_memmaps_dialog (app);
+}
+
+static void
+on_activate_open_files (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ create_openfiles_dialog (app);
+}
+
+static void
+on_activate_process_properties (GSimpleAction *, GVariant *, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ create_procproperties_dialog (app);
+}
+
+static void
+on_activate_radio (GSimpleAction *action, GVariant *parameter, gpointer data)
+{
+ g_action_change_state (G_ACTION (action), parameter);
+}
+
+static void
+on_activate_toggle (GSimpleAction *action, GVariant *parameter, gpointer data)
+{
+ GVariant *state = g_action_get_state (G_ACTION (action));
+ g_action_change_state (G_ACTION (action), g_variant_new_boolean (!g_variant_get_boolean (state)));
+ g_variant_unref (state);
+}
+
+static void
+on_activate_search (GSimpleAction *action, GVariant *parameter, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ GVariant *state = g_action_get_state (G_ACTION (action));
+ gboolean is_search_shortcut = g_variant_get_boolean (parameter);
+ gboolean is_search_bar = gtk_search_bar_get_search_mode (app->search_bar);
+ gtk_widget_set_visible (GTK_WIDGET (app->search_bar), is_search_bar || is_search_shortcut);
+ if (is_search_shortcut && is_search_bar) {
+ gtk_widget_grab_focus (GTK_WIDGET (app->search_entry));
+ } else {
+ g_action_change_state (G_ACTION (action), g_variant_new_boolean (!g_variant_get_boolean (state)));
+ }
+ g_variant_unref (state);
+}
+
+static void
+change_show_page_state (GSimpleAction *action, GVariant *state, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ auto state_var = Glib::wrap(state, true);
+ g_simple_action_set_state (action, state);
+ app->settings->set_value (GSM_SETTING_CURRENT_TAB, state_var);
+}
+
+static void
+change_show_processes_state (GSimpleAction *action, GVariant *state, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ auto state_var = Glib::wrap(state, true);
+ g_simple_action_set_state (action, state);
+ app->settings->set_value (GSM_SETTING_SHOW_WHOSE_PROCESSES, state_var);
+}
+
+static void
+change_show_dependencies_state (GSimpleAction *action, GVariant *state, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ auto state_var = Glib::wrap(state, true);
+ g_simple_action_set_state (action, state);
+ app->settings->set_value (GSM_SETTING_SHOW_DEPENDENCIES, state_var);
+}
+
+static void
+on_activate_priority (GSimpleAction *action, GVariant *parameter, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ g_action_change_state (G_ACTION (action), parameter);
+
+ const gint32 priority = g_variant_get_int32 (parameter);
+ switch (priority) {
+ case 32:
+ procdialog_create_renice_dialog (app);
+ break;
+ default:
+ renice (app, priority);
+ break;
+ }
+
+}
+
+static void
+change_priority_state (GSimpleAction *action, GVariant *state, gpointer data)
+{
+ g_simple_action_set_state (action, state);
+}
+
+static void
+update_page_activities (GsmApplication *app)
+{
+ const char *current_page = gtk_stack_get_visible_child_name (app->stack);
+
+ if (strcmp (current_page, "processes") == 0) {
+ GAction *search_action = g_action_map_lookup_action (G_ACTION_MAP (app->main_window),
+ "search");
+ proctable_update (app);
+ proctable_thaw (app);
+
+ gtk_widget_show (GTK_WIDGET (app->end_process_button));
+ gtk_widget_show (GTK_WIDGET (app->search_button));
+ gtk_widget_show (GTK_WIDGET (app->process_menu_button));
+ gtk_widget_hide (GTK_WIDGET (app->window_menu_button));
+
+ update_sensitivity (app);
+
+ if (g_variant_get_boolean (g_action_get_state (search_action)))
+ gtk_widget_grab_focus (GTK_WIDGET (app->search_entry));
+ else
+ gtk_widget_grab_focus (GTK_WIDGET (app->tree));
+ } else {
+ proctable_freeze (app);
+
+ gtk_widget_hide (GTK_WIDGET (app->end_process_button));
+ gtk_widget_hide (GTK_WIDGET (app->search_button));
+ gtk_widget_hide (GTK_WIDGET (app->process_menu_button));
+ gtk_widget_show (GTK_WIDGET (app->window_menu_button));
+
+ update_sensitivity (app);
+ }
+
+ if (strcmp (current_page, "resources") == 0) {
+ load_graph_start (app->cpu_graph);
+ load_graph_start (app->mem_graph);
+ load_graph_start (app->net_graph);
+ } else {
+ load_graph_stop (app->cpu_graph);
+ load_graph_stop (app->mem_graph);
+ load_graph_stop (app->net_graph);
+ }
+
+ if (strcmp (current_page, "disks") == 0) {
+ disks_update (app);
+ disks_thaw (app);
+ } else {
+ disks_freeze (app);
+ }
+}
+
+static void
+cb_change_current_page (GtkStack *stack, GParamSpec *pspec, gpointer data)
+{
+ update_page_activities ((GsmApplication *)data);
+}
+
+static gboolean
+cb_main_window_delete (GtkWidget *window, GdkEvent *event, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ app->shutdown ();
+
+ return TRUE;
+}
+
+static gboolean
+cb_main_window_state_changed (GtkWidget *window, GdkEventWindowState *event, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ auto current_page = app->settings->get_string (GSM_SETTING_CURRENT_TAB);
+ if (event->new_window_state & GDK_WINDOW_STATE_BELOW ||
+ event->new_window_state & GDK_WINDOW_STATE_ICONIFIED ||
+ event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)
+ {
+ if (current_page == "processes") {
+ proctable_freeze (app);
+ } else if (current_page == "resources") {
+ load_graph_stop (app->cpu_graph);
+ load_graph_stop (app->mem_graph);
+ load_graph_stop (app->net_graph);
+ } else if (current_page == "disks") {
+ disks_freeze (app);
+ }
+ } else {
+ if (current_page == "processes") {
+ proctable_update (app);
+ proctable_thaw (app);
+ } else if (current_page == "resources") {
+ load_graph_start (app->cpu_graph);
+ load_graph_start (app->mem_graph);
+ load_graph_start (app->net_graph);
+ } else if (current_page == "disks") {
+ disks_update (app);
+ disks_thaw (app);
+ }
+ }
+ return FALSE;
+}
+
+void
+create_main_window (GsmApplication *app)
+{
+ HdyApplicationWindow *main_window;
+ GtkStack *stack;
+ GMenuModel *window_menu_model;
+ GMenuModel *process_menu_model;
+ GdkDisplay *display;
+ GdkMonitor *monitor;
+ GdkRectangle monitor_geometry;
+
+ int width, height, xpos, ypos;
+
+ GtkBuilder *builder = gtk_builder_new();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/interface.ui", NULL);
+ gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/menus.ui", NULL);
+ gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/gtk/help-overlay.ui", NULL);
+
+ main_window = HDY_APPLICATION_WINDOW (gtk_builder_get_object (builder, "main_window"));
+ gtk_window_set_application (GTK_WINDOW (main_window), app->gobj());
+ gtk_widget_set_name (GTK_WIDGET (main_window), "gnome-system-monitor");
+ app->main_window = main_window;
+
+ gtk_application_window_set_help_overlay (GTK_APPLICATION_WINDOW (app->main_window),
+ GTK_SHORTCUTS_WINDOW (gtk_builder_get_object (builder, "help_overlay")));
+
+ g_settings_get (app->settings->gobj(), GSM_SETTING_WINDOW_STATE, "(iiii)",
+ &width, &height, &xpos, &ypos);
+
+ display = gdk_display_get_default ();
+ monitor = gdk_display_get_monitor_at_point (display, xpos, ypos);
+ if (monitor == NULL) {
+ monitor = gdk_display_get_monitor (display, 0);
+ }
+ gdk_monitor_get_geometry (monitor, &monitor_geometry);
+
+ width = CLAMP (width, 50, monitor_geometry.width);
+ height = CLAMP (height, 50, monitor_geometry.height);
+ xpos = CLAMP (xpos, 0, monitor_geometry.width - width);
+ ypos = CLAMP (ypos, 0, monitor_geometry.height - height);
+
+ gtk_window_set_default_size (GTK_WINDOW (main_window), width, height);
+ gtk_window_move (GTK_WINDOW (main_window), xpos, ypos);
+ if (app->settings->get_boolean (GSM_SETTING_MAXIMIZED))
+ gtk_window_maximize (GTK_WINDOW (main_window));
+
+ app->process_menu_button = GTK_MENU_BUTTON (gtk_builder_get_object (builder, "process_menu_button"));
+ process_menu_model = G_MENU_MODEL (gtk_builder_get_object (builder, "process-window-menu"));
+ gtk_menu_button_set_menu_model (app->process_menu_button, process_menu_model);
+
+ app->window_menu_button = GTK_MENU_BUTTON (gtk_builder_get_object (builder, "window_menu_button"));
+ window_menu_model = G_MENU_MODEL (gtk_builder_get_object (builder, "generic-window-menu"));
+ gtk_menu_button_set_menu_model (app->window_menu_button, window_menu_model);
+
+ app->end_process_button = GTK_BUTTON (gtk_builder_get_object (builder, "end_process_button"));
+
+ app->search_button = GTK_BUTTON (gtk_builder_get_object (builder, "search_button"));
+
+ GActionEntry win_action_entries[] = {
+ { "about", on_activate_about, NULL, NULL, NULL },
+ { "show-help-overlay", on_activate_keyboard_shortcuts, NULL, NULL, NULL},
+ { "search", on_activate_search, "b", "false", NULL },
+ { "send-signal-stop", on_activate_send_signal, "i", NULL, NULL },
+ { "send-signal-cont", on_activate_send_signal, "i", NULL, NULL },
+ { "send-signal-end", on_activate_send_signal, "i", NULL, NULL },
+ { "send-signal-kill", on_activate_send_signal, "i", NULL, NULL },
+ { "priority", on_activate_priority, "i", "@i 0", change_priority_state },
+ { "set-affinity", on_activate_set_affinity, NULL, NULL, NULL },
+ { "memory-maps", on_activate_memory_maps, NULL, NULL, NULL },
+ { "open-files", on_activate_open_files, NULL, NULL, NULL },
+ { "process-properties", on_activate_process_properties, NULL, NULL, NULL },
+ { "refresh", on_activate_refresh, NULL, NULL, NULL },
+ { "show-page", on_activate_radio, "s", "'resources'", change_show_page_state },
+ { "show-whose-processes", on_activate_radio, "s", "'all'", change_show_processes_state },
+ { "show-dependencies", on_activate_toggle, NULL, "false", change_show_dependencies_state }
+ };
+
+ g_action_map_add_action_entries (G_ACTION_MAP (main_window),
+ win_action_entries,
+ G_N_ELEMENTS (win_action_entries),
+ app);
+
+ GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET (main_window));
+ GdkVisual* visual = gdk_screen_get_rgba_visual(screen);
+
+ /* use visual, if available */
+ if (visual)
+ gtk_widget_set_visual(GTK_WIDGET (main_window), visual);
+
+ /* create the main stack */
+ app->stack = stack = GTK_STACK (gtk_builder_get_object (builder, "stack"));
+
+ create_proc_view(app, builder);
+
+ create_sys_view (app, builder);
+
+ create_disk_view (app, builder);
+
+ g_settings_bind (app->settings->gobj (), GSM_SETTING_CURRENT_TAB, stack, "visible-child-name", G_SETTINGS_BIND_DEFAULT);
+
+ g_signal_connect (G_OBJECT (stack), "notify::visible-child",
+ G_CALLBACK (cb_change_current_page), app);
+
+ g_signal_connect (G_OBJECT (main_window), "delete_event",
+ G_CALLBACK (cb_main_window_delete),
+ app);
+ g_signal_connect (G_OBJECT (main_window), "window-state-event",
+ G_CALLBACK (cb_main_window_state_changed),
+ app);
+
+ GAction *action;
+ action = g_action_map_lookup_action (G_ACTION_MAP (main_window),
+ "show-dependencies");
+ g_action_change_state (action,
+ g_settings_get_value (app->settings->gobj (), GSM_SETTING_SHOW_DEPENDENCIES));
+
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (main_window),
+ "show-whose-processes");
+ g_action_change_state (action,
+ g_settings_get_value (app->settings->gobj (), GSM_SETTING_SHOW_WHOSE_PROCESSES));
+
+ gtk_widget_show (GTK_WIDGET (main_window));
+
+ update_page_activities (app);
+
+ g_object_unref (G_OBJECT (builder));
+}
+
+static gboolean
+scroll_to_selection (gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ GList* paths = gtk_tree_selection_get_selected_rows (app->selection, NULL);
+ guint length = g_list_length(paths);
+ if (length > 0) {
+ GtkTreePath* last_path = (GtkTreePath*) g_list_nth_data(paths, length - 1);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (app->tree), last_path, NULL, FALSE, 0.0, 0.0);
+ }
+
+ g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
+ return FALSE;
+}
+
+void
+update_sensitivity(GsmApplication *app)
+{
+ const char * const selected_actions[] = { "send-signal-stop",
+ "send-signal-cont",
+ "send-signal-end",
+ "send-signal-kill",
+ "priority",
+ "set-affinity",
+ "memory-maps",
+ "open-files",
+ "process-properties" };
+
+ const char * const processes_actions[] = { "refresh",
+ "search",
+ "show-whose-processes",
+ "show-dependencies" };
+
+ size_t i;
+ gboolean processes_sensitivity, selected_sensitivity;
+ GAction *action;
+
+ processes_sensitivity = (strcmp (gtk_stack_get_visible_child_name (GTK_STACK (app->stack)), "processes") == 0);
+ selected_sensitivity = gtk_tree_selection_count_selected_rows (app->selection) > 0;
+
+ for (i = 0; i != G_N_ELEMENTS (processes_actions); ++i) {
+ action = g_action_map_lookup_action (G_ACTION_MAP (app->main_window),
+ processes_actions[i]);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ processes_sensitivity);
+ }
+
+ for (i = 0; i != G_N_ELEMENTS (selected_actions); ++i) {
+ action = g_action_map_lookup_action (G_ACTION_MAP (app->main_window),
+ selected_actions[i]);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ processes_sensitivity & selected_sensitivity);
+ }
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (app->proc_actionbar_revealer),
+ selected_sensitivity);
+
+ // Scrolls the table to selected row. Useful when the last row is obstructed by the revealer
+ guint duration_ms = gtk_revealer_get_transition_duration (GTK_REVEALER (app->proc_actionbar_revealer));
+ g_timeout_add (duration_ms, scroll_to_selection, app);
+}
+
diff --git a/src/interface.h b/src/interface.h
new file mode 100644
index 0000000..0520760
--- /dev/null
+++ b/src/interface.h
@@ -0,0 +1,30 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman - main window
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _GSM_INTERFACE_H_
+#define _GSM_INTERFACE_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "application.h"
+
+void create_main_window (GsmApplication *app);
+void update_sensitivity (GsmApplication *app);
+
+#endif /* _GSM_INTERFACE_H_ */
diff --git a/src/legacy/e_date.c b/src/legacy/e_date.c
new file mode 100644
index 0000000..2697e89
--- /dev/null
+++ b/src/legacy/e_date.c
@@ -0,0 +1,215 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+#include "e_date.h"
+
+/*
+ all this code comes from evolution
+ - e-util.c
+ - message-list.c
+*/
+
+
+static size_t e_strftime(char *s, size_t max, const char *fmt, const struct tm *tm)
+{
+#ifdef HAVE_LKSTRFTIME
+ return strftime(s, max, fmt, tm);
+#else
+ char *c, *ffmt, *ff;
+ size_t ret;
+
+ ffmt = g_strdup(fmt);
+ ff = ffmt;
+ while ((c = strstr(ff, "%l")) != NULL) {
+ c[1] = 'I';
+ ff = c;
+ }
+
+ ff = ffmt;
+ while ((c = strstr(ff, "%k")) != NULL) {
+ c[1] = 'H';
+ ff = c;
+ }
+
+ ret = strftime(s, max, ffmt, tm);
+ g_free(ffmt);
+ return ret;
+#endif
+}
+
+
+/**
+ * Function to do a last minute fixup of the AM/PM stuff if the locale
+ * and gettext haven't done it right. Most English speaking countries
+ * except the USA use the 24 hour clock (UK, Australia etc). However
+ * since they are English nobody bothers to write a language
+ * translation (gettext) file. So the locale turns off the AM/PM, but
+ * gettext does not turn on the 24 hour clock. Leaving a mess.
+ *
+ * This routine checks if AM/PM are defined in the locale, if not it
+ * forces the use of the 24 hour clock.
+ *
+ * The function itself is a front end on strftime and takes exactly
+ * the same arguments.
+ *
+ * TODO: Actually remove the '%p' from the fixed up string so that
+ * there isn't a stray space.
+ **/
+
+static size_t e_strftime_fix_am_pm(char *s, size_t max, const char *fmt, const struct tm *tm)
+{
+ char buf[10];
+ char *sp;
+ char *ffmt;
+ size_t ret;
+
+ if (strstr(fmt, "%p")==NULL && strstr(fmt, "%P")==NULL) {
+ /* No AM/PM involved - can use the fmt string directly */
+ ret=e_strftime(s, max, fmt, tm);
+ } else {
+ /* Get the AM/PM symbol from the locale */
+ e_strftime (buf, 10, "%p", tm);
+
+ if (buf[0]) {
+ /**
+ * AM/PM have been defined in the locale
+ * so we can use the fmt string directly
+ **/
+ ret=e_strftime(s, max, fmt, tm);
+ } else {
+ /**
+ * No AM/PM defined by locale
+ * must change to 24 hour clock
+ **/
+ ffmt=g_strdup(fmt);
+ for (sp=ffmt; (sp=strstr(sp, "%l")); sp++) {
+ /**
+ * Maybe this should be 'k', but I have never
+ * seen a 24 clock actually use that format
+ **/
+ sp[1]='H';
+ }
+ for (sp=ffmt; (sp=strstr(sp, "%I")); sp++) {
+ sp[1]='H';
+ }
+ ret=e_strftime(s, max, ffmt, tm);
+ g_free(ffmt);
+ }
+ }
+ return(ret);
+}
+
+static size_t
+e_utf8_strftime_fix_am_pm(char *s, size_t max, const char *fmt, const struct tm *tm)
+{
+ size_t sz, ret;
+ char *locale_fmt, *buf;
+
+ locale_fmt = g_locale_from_utf8(fmt, -1, NULL, &sz, NULL);
+ if (!locale_fmt)
+ return 0;
+
+ ret = e_strftime_fix_am_pm(s, max, locale_fmt, tm);
+ if (!ret) {
+ g_free (locale_fmt);
+ return 0;
+ }
+
+ buf = g_locale_to_utf8(s, ret, NULL, &sz, NULL);
+ if (!buf) {
+ g_free (locale_fmt);
+ return 0;
+ }
+
+ if (sz >= max) {
+ char *tmp = buf + max - 1;
+ tmp = g_utf8_find_prev_char(buf, tmp);
+ if (tmp)
+ sz = tmp - buf;
+ else
+ sz = 0;
+ }
+ memcpy(s, buf, sz);
+ s[sz] = '\0';
+ g_free(locale_fmt);
+ g_free(buf);
+ return sz;
+}
+
+
+static char *
+filter_date (time_t date)
+{
+ time_t nowdate = time(NULL);
+ time_t yesdate;
+ struct tm then, now, yesterday;
+ char buf[26];
+ gboolean done = FALSE;
+
+ if (date == 0)
+ // xgettext: ? stands for unknown
+ return g_strdup (_("?"));
+
+ localtime_r (&date, &then);
+ localtime_r (&nowdate, &now);
+ if (then.tm_mday == now.tm_mday &&
+ then.tm_mon == now.tm_mon &&
+ then.tm_year == now.tm_year) {
+ e_utf8_strftime_fix_am_pm (buf, 26, _("Today %l∶%M %p"), &then);
+ done = TRUE;
+ }
+ if (!done) {
+ yesdate = nowdate - 60 * 60 * 24;
+ localtime_r (&yesdate, &yesterday);
+ if (then.tm_mday == yesterday.tm_mday &&
+ then.tm_mon == yesterday.tm_mon &&
+ then.tm_year == yesterday.tm_year) {
+ e_utf8_strftime_fix_am_pm (buf, 26, _("Yesterday %l∶%M %p"), &then);
+ done = TRUE;
+ }
+ }
+ if (!done) {
+ int i;
+ for (i = 2; i < 7; i++) {
+ yesdate = nowdate - 60 * 60 * 24 * i;
+ localtime_r (&yesdate, &yesterday);
+ if (then.tm_mday == yesterday.tm_mday &&
+ then.tm_mon == yesterday.tm_mon &&
+ then.tm_year == yesterday.tm_year) {
+ e_utf8_strftime_fix_am_pm (buf, 26, _("%a %l∶%M %p"), &then);
+ done = TRUE;
+ break;
+ }
+ }
+ }
+ if (!done) {
+ if (then.tm_year == now.tm_year) {
+ e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %l∶%M %p"), &then);
+ } else {
+ e_utf8_strftime_fix_am_pm (buf, 26, _("%b %d %Y"), &then);
+ }
+ }
+#if 0
+#ifdef CTIME_R_THREE_ARGS
+ ctime_r (&date, buf, 26);
+#else
+ ctime_r (&date, buf);
+#endif
+#endif
+
+ return g_strdup (buf);
+}
+
+
+
+
+char *
+procman_format_date_for_display(time_t d)
+{
+ return filter_date(d);
+}
diff --git a/src/legacy/e_date.h b/src/legacy/e_date.h
new file mode 100644
index 0000000..05ab097
--- /dev/null
+++ b/src/legacy/e_date.h
@@ -0,0 +1,15 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_E_DATE_H_
+#define _GSM_E_DATE_H_
+
+#include <time.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+char *
+procman_format_date_for_display(time_t d);
+
+G_END_DECLS
+
+#endif /* _GSM_E_DATE_H_ */
diff --git a/src/legacy/gsm_color_button.c b/src/legacy/gsm_color_button.c
new file mode 100644
index 0000000..02a8ad3
--- /dev/null
+++ b/src/legacy/gsm_color_button.c
@@ -0,0 +1,859 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Gnome system monitor colour pickers
+ * Copyright (C) 2007 Karl Lattimer <karl@qdh.org.uk>
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with the software; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <librsvg/rsvg.h>
+
+#include "gsm_color_button.h"
+
+typedef struct
+{
+ GtkColorChooserDialog *cc_dialog; /* Color chooser dialog */
+
+ gchar *title; /* Title for the color selection window */
+
+ GdkRGBA color;
+ gdouble fraction; /* Only used by GSMCP_TYPE_PIE */
+ guint type;
+ cairo_surface_t *image_buffer;
+ gdouble highlight;
+ gboolean button_down;
+ gboolean in_button;
+} GsmColorButtonPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsmColorButton, gsm_color_button, GTK_TYPE_DRAWING_AREA);
+
+/* Properties */
+enum
+{
+ PROP_0,
+ PROP_PERCENTAGE,
+ PROP_TITLE,
+ PROP_COLOR,
+ PROP_TYPE,
+};
+
+/* Signals */
+enum
+{
+ COLOR_SET,
+ LAST_SIGNAL
+};
+
+#define GSMCP_MIN_WIDTH 15
+#define GSMCP_MIN_HEIGHT 15
+
+static void gsm_color_button_class_init (GsmColorButtonClass * klass);
+static void gsm_color_button_init (GsmColorButton * color_button);
+static void gsm_color_button_finalize (GObject * object);
+static void gsm_color_button_set_property (GObject * object, guint param_id,
+ const GValue * value,
+ GParamSpec * pspec);
+static void gsm_color_button_get_property (GObject * object, guint param_id,
+ GValue * value,
+ GParamSpec * pspec);
+static gboolean gsm_color_button_draw (GtkWidget *widget,
+ cairo_t *cr);
+static void gsm_color_button_get_preferred_width (GtkWidget * widget,
+ gint * minimum,
+ gint * natural);
+static void gsm_color_button_get_preferred_height (GtkWidget * widget,
+ gint * minimum,
+ gint * natural);
+static gint gsm_color_button_pressed (GtkWidget * widget,
+ GdkEventButton * event);
+static gint gsm_color_button_released (GtkWidget * widget,
+ GdkEventButton * event);
+static gboolean gsm_color_button_enter_notify (GtkWidget * widget,
+ GdkEventCrossing * event);
+static gboolean gsm_color_button_leave_notify (GtkWidget * widget,
+ GdkEventCrossing * event);
+/* source side drag signals */
+static void gsm_color_button_drag_begin (GtkWidget * widget,
+ GdkDragContext * context,
+ gpointer data);
+static void gsm_color_button_drag_data_get (GtkWidget * widget,
+ GdkDragContext * context,
+ GtkSelectionData * selection_data,
+ guint info, guint time,
+ GsmColorButton * color_button);
+
+/* target side drag signals */
+static void gsm_color_button_drag_data_received (GtkWidget * widget,
+ GdkDragContext * context,
+ gint x,
+ gint y,
+ GtkSelectionData *
+ selection_data, guint info,
+ guint32 time,
+ GsmColorButton *
+ color_button);
+
+
+static guint color_button_signals[LAST_SIGNAL] = { 0 };
+
+static const GtkTargetEntry drop_types[] = { {"application/x-color", 0, 0} };
+
+static void
+gsm_color_button_class_init (GsmColorButtonClass * klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->get_property = gsm_color_button_get_property;
+ gobject_class->set_property = gsm_color_button_set_property;
+ gobject_class->finalize = gsm_color_button_finalize;
+ widget_class->draw = gsm_color_button_draw;
+ widget_class->get_preferred_width = gsm_color_button_get_preferred_width;
+ widget_class->get_preferred_height = gsm_color_button_get_preferred_height;
+ widget_class->button_release_event = gsm_color_button_released;
+ widget_class->button_press_event = gsm_color_button_pressed;
+ widget_class->enter_notify_event = gsm_color_button_enter_notify;
+ widget_class->leave_notify_event = gsm_color_button_leave_notify;
+
+ g_object_class_install_property (gobject_class,
+ PROP_PERCENTAGE,
+ g_param_spec_double ("fraction",
+ _("Fraction"),
+ // TRANSLATORS: description of the pie color picker's (mem, swap) filled percentage property
+ _("Percentage full for pie color pickers"),
+ 0, 1, 0.5,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_TITLE,
+ g_param_spec_string ("title",
+ _("Title"),
+ _("The title of the color selection dialog"),
+ _("Pick a Color"),
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_COLOR,
+ g_param_spec_boxed ("color",
+ _("Current Color"),
+ _("The selected color"),
+ GDK_TYPE_RGBA,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_TYPE,
+ g_param_spec_uint ("type", _("Type"),
+ _("Type of color picker"),
+ 0, 4, 0,
+ G_PARAM_READWRITE));
+
+ color_button_signals[COLOR_SET] = g_signal_new ("color-set",
+ G_TYPE_FROM_CLASS
+ (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+static cairo_surface_t *
+fill_image_buffer_from_resource (cairo_t *cr, const char *path)
+{
+ GBytes *bytes;
+ const guint8 *data;
+ gsize len;
+ GError *error = NULL;
+ RsvgHandle *handle;
+ cairo_surface_t *tmp_surface;
+ cairo_t *tmp_cr;
+
+ bytes = g_resources_lookup_data (path, 0 , NULL);
+ data = g_bytes_get_data (bytes, &len);
+
+ handle = rsvg_handle_new_from_data (data, len, &error);
+
+ if (handle == NULL) {
+ g_warning("rsvg_handle_new_from_data(\"%s\") failed: %s",
+ path, (error ? error->message : "unknown error"));
+ if (error)
+ g_error_free(error);
+ g_bytes_unref(bytes);
+ return NULL;
+ }
+
+ tmp_surface = cairo_surface_create_similar (cairo_get_target (cr),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ 32, 32);
+ tmp_cr = cairo_create (tmp_surface);
+ rsvg_handle_render_cairo (handle, tmp_cr);
+ cairo_destroy (tmp_cr);
+ g_object_unref (handle);
+ g_bytes_unref(bytes);
+ return tmp_surface;
+}
+
+static gboolean
+gsm_color_button_draw (GtkWidget *widget, cairo_t * cr)
+{
+ GsmColorButton *color_button = GSM_COLOR_BUTTON (widget);
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (color_button);
+ GdkRGBA *color = gdk_rgba_copy(&priv->color);
+ cairo_path_t *path = NULL;
+ gint width, height;
+ gdouble radius, arc_start, arc_end;
+ gdouble highlight_factor;
+ gboolean sensitive = gtk_widget_get_sensitive (widget);
+
+ if (sensitive && priv->highlight > 0) {
+ highlight_factor = 0.125 * priv->highlight;
+
+ color->red = MIN (1.0, color->red + highlight_factor);
+
+ color->blue = MIN (1.0, color->blue + highlight_factor) ;
+
+ color->green = MIN (1.0, color->green + highlight_factor);
+ } else if (!sensitive) {
+ GtkStyleContext *context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_color (context, gtk_widget_get_state_flags (widget), color);
+ }
+ gdk_cairo_set_source_rgba (cr, color);
+ gdk_rgba_free(color);
+ width = gdk_window_get_width (gtk_widget_get_window (widget));
+ height = gdk_window_get_height(gtk_widget_get_window (widget));
+
+ switch (priv->type)
+ {
+ case GSMCP_TYPE_CPU:
+ //gtk_widget_set_size_request (widget, GSMCP_MIN_WIDTH, GSMCP_MIN_HEIGHT);
+ cairo_paint (cr);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ cairo_rectangle (cr, 0.5, 0.5, width - 1, height - 1);
+ cairo_stroke (cr);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 1, 1, 1, 0.4);
+ cairo_rectangle (cr, 1.5, 1.5, width - 3, height - 3);
+ cairo_stroke (cr);
+ break;
+ case GSMCP_TYPE_PIE:
+ if (width < 32) // 32px minimum size
+ gtk_widget_set_size_request (widget, 32, 32);
+ if (width < height)
+ radius = width / 2;
+ else
+ radius = height / 2;
+
+ arc_start = -G_PI_2 + 2 * G_PI * priv->fraction;
+ arc_end = -G_PI_2;
+
+ cairo_set_line_width (cr, 1);
+
+ // Draw external stroke and fill
+ if (priv->fraction < 0.01) {
+ cairo_arc (cr, (width / 2) + .5, (height / 2) + .5, 4.5,
+ 0, 2 * G_PI);
+ } else if (priv->fraction > 0.99) {
+ cairo_arc (cr, (width / 2) + .5, (height / 2) + .5, radius - 2.25,
+ 0, 2 * G_PI);
+ } else {
+ cairo_arc_negative (cr, (width / 2) + .5, (height / 2) + .5, radius - 2.25,
+ arc_start, arc_end);
+ cairo_arc_negative (cr, (width / 2) + .5, (height / 2) + .5, 4.5,
+ arc_end, arc_start);
+ cairo_arc_negative (cr, (width / 2) + .5, (height / 2) + .5, radius - 2.25,
+ arc_start, arc_start);
+ }
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.7);
+ cairo_stroke (cr);
+
+ // Draw internal highlight
+ cairo_set_source_rgba (cr, 1, 1, 1, 0.45);
+ cairo_set_line_width (cr, 1);
+
+ if (priv->fraction < 0.03) {
+ cairo_arc (cr, (width / 2) + .5, (height / 2) + .5, 3.25,
+ 0, 2 * G_PI);
+ } else if (priv->fraction > 0.99) {
+ cairo_arc (cr, (width / 2) + .5, (height / 2) + .5, radius - 3.5,
+ 0, 2 * G_PI);
+ } else {
+ cairo_arc_negative (cr, (width / 2) + .5, (height / 2) + .5, radius - 3.5,
+ arc_start + (1 / (radius - 3.75)),
+ arc_end - (1 / (radius - 3.75)));
+ cairo_arc_negative (cr, (width / 2) + .5, (height / 2) + .5, 3.25,
+ arc_end - (1 / (radius - 3.75)),
+ arc_start + (1 / (radius - 3.75)));
+ cairo_arc_negative (cr, (width / 2) + .5, (height / 2) + .5, radius - 3.5,
+ arc_start + (1 / (radius - 3.75)),
+ arc_start + (1 / (radius - 3.75)));
+ }
+ cairo_stroke (cr);
+
+ // Draw external shape
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.2);
+ cairo_arc (cr, (width / 2) + .5, (height / 2) + .5, radius - 1.25, 0,
+ G_PI * 2);
+ cairo_stroke (cr);
+
+ break;
+ case GSMCP_TYPE_NETWORK_IN:
+ if (priv->image_buffer == NULL)
+ priv->image_buffer =
+ fill_image_buffer_from_resource (cr, "/org/gnome/gnome-system-monitor/pixmaps/download.svg");
+ gtk_widget_set_size_request (widget, 32, 32);
+ cairo_move_to (cr, 8.5, 1.5);
+ cairo_line_to (cr, 23.5, 1.5);
+ cairo_line_to (cr, 23.5, 11.5);
+ cairo_line_to (cr, 29.5, 11.5);
+ cairo_line_to (cr, 16.5, 27.5);
+ cairo_line_to (cr, 15.5, 27.5);
+ cairo_line_to (cr, 2.5, 11.5);
+ cairo_line_to (cr, 8.5, 11.5);
+ cairo_line_to (cr, 8.5, 1.5);
+ cairo_close_path (cr);
+ path = cairo_copy_path (cr);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
+ cairo_set_line_width (cr, 1);
+ cairo_fill_preserve (cr);
+ cairo_set_miter_limit (cr, 5.0);
+ cairo_stroke (cr);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ cairo_append_path (cr, path);
+ cairo_path_destroy(path);
+ cairo_stroke (cr);
+ cairo_set_source_surface (cr, priv->image_buffer, 0.0,
+ 0.0);
+ cairo_paint (cr);
+
+ break;
+ case GSMCP_TYPE_NETWORK_OUT:
+ if (priv->image_buffer == NULL)
+ priv->image_buffer =
+ fill_image_buffer_from_resource (cr, "/org/gnome/gnome-system-monitor/pixmaps/upload.svg");
+ gtk_widget_set_size_request (widget, 32, 32);
+ cairo_move_to (cr, 16.5, 1.5);
+ cairo_line_to (cr, 29.5, 17.5);
+ cairo_line_to (cr, 23.5, 17.5);
+ cairo_line_to (cr, 23.5, 27.5);
+ cairo_line_to (cr, 8.5, 27.5);
+ cairo_line_to (cr, 8.5, 17.5);
+ cairo_line_to (cr, 2.5, 17.5);
+ cairo_line_to (cr, 15.5, 1.5);
+ cairo_line_to (cr, 16.5, 1.5);
+ cairo_close_path (cr);
+ path = cairo_copy_path (cr);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
+ cairo_set_line_width (cr, 1);
+ cairo_fill_preserve (cr);
+ cairo_set_miter_limit (cr, 5.0);
+ cairo_stroke (cr);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ cairo_append_path (cr, path);
+ cairo_path_destroy(path);
+ cairo_stroke (cr);
+ cairo_set_source_surface (cr, priv->image_buffer, 0.0,
+ 0.0);
+ cairo_paint (cr);
+
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+gsm_color_button_get_preferred_width (GtkWidget * widget,
+ gint * minimum,
+ gint * natural)
+{
+ if (minimum)
+ *minimum = GSMCP_MIN_WIDTH;
+ if (natural)
+ *natural = GSMCP_MIN_WIDTH;
+}
+
+static void
+gsm_color_button_get_preferred_height (GtkWidget * widget,
+ gint * minimum,
+ gint * natural)
+{
+ if (minimum)
+ *minimum = GSMCP_MIN_HEIGHT;
+ if (natural)
+ *natural = GSMCP_MIN_HEIGHT;
+}
+
+static void
+gsm_color_button_drag_data_received (GtkWidget * widget,
+ GdkDragContext * context,
+ gint x,
+ gint y,
+ GtkSelectionData * selection_data,
+ guint info,
+ guint32 time,
+ GsmColorButton * color_button)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (color_button);
+
+ gint length;
+ guint16 *dropped;
+
+ length = gtk_selection_data_get_length (selection_data);
+
+ if (length < 0)
+ return;
+
+ /* We accept drops with the wrong format, since the KDE color
+ * chooser incorrectly drops application/x-color with format 8.
+ */
+ if (length != 8)
+ {
+ g_warning (_("Received invalid color data\n"));
+ return;
+ }
+
+
+ dropped = (guint16 *) gtk_selection_data_get_data (selection_data);
+
+ priv->color.red = (gdouble)dropped[0] / 0xffff;
+ priv->color.green = (gdouble)dropped[1] / 0xffff;
+ priv->color.blue = (gdouble)dropped[2] / 0xffff;
+
+ gtk_widget_queue_draw (GTK_WIDGET (color_button));
+
+ g_signal_emit (color_button, color_button_signals[COLOR_SET], 0);
+
+ g_object_freeze_notify (G_OBJECT (color_button));
+ g_object_notify (G_OBJECT (color_button), "color");
+ g_object_thaw_notify (G_OBJECT (color_button));
+}
+
+
+static void
+set_color_icon (GdkDragContext * context, GdkRGBA * color)
+{
+ GdkPixbuf *pixbuf;
+ guint32 pixel;
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 48, 32);
+
+ pixel = ((guint32)(color->red * 0xff) << 24) |
+ ((guint32)(color->green * 0xff) << 16) |
+ ((guint32)(color->blue * 0xff) << 8);
+
+ gdk_pixbuf_fill (pixbuf, pixel);
+
+ gtk_drag_set_icon_pixbuf (context, pixbuf, -2, -2);
+ g_object_unref (pixbuf);
+}
+
+static void
+gsm_color_button_drag_begin (GtkWidget * widget,
+ GdkDragContext * context, gpointer data)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (GSM_COLOR_BUTTON (data));
+ set_color_icon (context, &priv->color);
+}
+
+static void
+gsm_color_button_drag_data_get (GtkWidget * widget,
+ GdkDragContext * context,
+ GtkSelectionData * selection_data,
+ guint info,
+ guint time, GsmColorButton * color_button)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (color_button);
+ guint16 dropped[4];
+
+ dropped[0] = priv->color.red * 0xffff;
+ dropped[1] = priv->color.green * 0xffff;
+ dropped[2] = priv->color.blue * 0xffff;
+ dropped[3] = 65535; // This widget doesn't care about alpha
+
+ gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
+ 16, (guchar *) dropped, 8);
+}
+
+
+static void
+gsm_color_button_init (GsmColorButton * color_button)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (color_button);
+
+ priv->color.red = 0;
+ priv->color.green = 0;
+ priv->color.blue = 0;
+ priv->fraction = 0.5;
+ priv->type = GSMCP_TYPE_CPU;
+ priv->image_buffer = NULL;
+ priv->title = g_strdup (_("Pick a Color")); /* default title */
+ priv->in_button = FALSE;
+ priv->button_down = FALSE;
+
+ gtk_drag_dest_set (GTK_WIDGET (color_button),
+ GTK_DEST_DEFAULT_MOTION |
+ GTK_DEST_DEFAULT_HIGHLIGHT |
+ GTK_DEST_DEFAULT_DROP, drop_types, 1, GDK_ACTION_COPY);
+ gtk_drag_source_set (GTK_WIDGET (color_button),
+ GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
+ drop_types, 1, GDK_ACTION_COPY);
+ g_signal_connect (color_button, "drag_begin",
+ G_CALLBACK (gsm_color_button_drag_begin), color_button);
+ g_signal_connect (color_button, "drag_data_received",
+ G_CALLBACK (gsm_color_button_drag_data_received),
+ color_button);
+ g_signal_connect (color_button, "drag_data_get",
+ G_CALLBACK (gsm_color_button_drag_data_get),
+ color_button);
+
+ gtk_widget_add_events (GTK_WIDGET(color_button), GDK_ENTER_NOTIFY_MASK
+ | GDK_LEAVE_NOTIFY_MASK);
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET(color_button), _("Click to set graph colors"));
+}
+
+static void
+gsm_color_button_finalize (GObject * object)
+{
+ GsmColorButton *color_button = GSM_COLOR_BUTTON (object);
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (color_button);
+
+ if (priv->cc_dialog != NULL)
+ gtk_widget_destroy (GTK_WIDGET (priv->cc_dialog));
+ priv->cc_dialog = NULL;
+
+ g_free (priv->title);
+ priv->title = NULL;
+
+ cairo_surface_destroy (priv->image_buffer);
+ priv->image_buffer = NULL;
+
+ G_OBJECT_CLASS (gsm_color_button_parent_class)->finalize (object);
+}
+
+GsmColorButton *
+gsm_color_button_new (const GdkRGBA * color, guint type)
+{
+ return g_object_new (GSM_TYPE_COLOR_BUTTON, "color", color, "type", type,
+ "visible", TRUE, NULL);
+}
+
+static void
+dialog_response (GtkWidget * widget, GtkResponseType response, gpointer data)
+{
+ GsmColorButton *color_button = GSM_COLOR_BUTTON (data);
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (color_button);
+ GtkColorChooser *color_chooser;
+
+ if (response == GTK_RESPONSE_OK) {
+ color_chooser = GTK_COLOR_CHOOSER (priv->cc_dialog);
+
+ gtk_color_chooser_get_rgba (color_chooser, &priv->color);
+
+ gtk_widget_hide (GTK_WIDGET (priv->cc_dialog));
+
+ gtk_widget_queue_draw (GTK_WIDGET (color_button));
+
+ g_signal_emit (color_button, color_button_signals[COLOR_SET], 0);
+
+ g_object_freeze_notify (G_OBJECT (color_button));
+ g_object_notify (G_OBJECT (color_button), "color");
+ g_object_thaw_notify (G_OBJECT (color_button));
+ }
+ else /* (response == GTK_RESPONSE_CANCEL) */
+ gtk_widget_hide (GTK_WIDGET (priv->cc_dialog));
+}
+
+static gboolean
+dialog_destroy (GtkWidget * widget, gpointer data)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (GSM_COLOR_BUTTON (data));
+
+ priv->cc_dialog = NULL;
+
+ return FALSE;
+}
+
+static gint
+gsm_color_button_clicked (GtkWidget * widget, GdkEventButton * event)
+{
+ GsmColorButton *color_button = GSM_COLOR_BUTTON (widget);
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (color_button);
+
+ /* if dialog already exists, make sure it's shown and raised */
+ if (!priv->cc_dialog)
+ {
+ /* Create the dialog and connects its buttons */
+ GtkColorChooserDialog *cc_dialog;
+ GtkWidget *parent;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (color_button));
+ if (!gtk_widget_is_toplevel (parent))
+ parent = NULL;
+
+ cc_dialog = GTK_COLOR_CHOOSER_DIALOG (gtk_color_chooser_dialog_new (priv->title, GTK_WINDOW (parent)));
+
+ gtk_window_set_modal (GTK_WINDOW (cc_dialog), TRUE);
+
+ g_signal_connect (cc_dialog, "response",
+ G_CALLBACK (dialog_response), color_button);
+
+ g_signal_connect (cc_dialog, "destroy",
+ G_CALLBACK (dialog_destroy), color_button);
+
+ priv->cc_dialog = cc_dialog;
+ }
+
+ gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (priv->cc_dialog),
+ &priv->color);
+
+ gtk_window_present (GTK_WINDOW (priv->cc_dialog));
+ return 0;
+}
+
+static gint
+gsm_color_button_pressed (GtkWidget * widget, GdkEventButton * event)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (GSM_COLOR_BUTTON (widget));
+
+ if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1))
+ priv->button_down = TRUE;
+ return 0;
+}
+
+static gint
+gsm_color_button_released (GtkWidget * widget, GdkEventButton * event)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (GSM_COLOR_BUTTON (widget));
+ if (priv->button_down && priv->in_button)
+ gsm_color_button_clicked (widget, event);
+ priv->button_down = FALSE;
+ return 0;
+}
+
+
+static gboolean
+gsm_color_button_enter_notify (GtkWidget * widget, GdkEventCrossing * event)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (GSM_COLOR_BUTTON (widget));
+ priv->highlight = 1.0;
+ priv->in_button = TRUE;
+ gtk_widget_queue_draw(widget);
+ return FALSE;
+}
+
+static gboolean
+gsm_color_button_leave_notify (GtkWidget * widget, GdkEventCrossing * event)
+{
+ GsmColorButtonPrivate *priv = gsm_color_button_get_instance_private (GSM_COLOR_BUTTON (widget));
+ priv->highlight = 0;
+ priv->in_button = FALSE;
+ gtk_widget_queue_draw(widget);
+ return FALSE;
+}
+
+guint
+gsm_color_button_get_cbtype (GsmColorButton * color_button)
+{
+ GsmColorButtonPrivate *priv;
+
+ g_return_val_if_fail (GSM_IS_COLOR_BUTTON (color_button), 0);
+
+ priv = gsm_color_button_get_instance_private (color_button);
+ return priv->type;
+}
+
+void
+gsm_color_button_set_cbtype (GsmColorButton * color_button, guint type)
+{
+ GsmColorButtonPrivate *priv;
+
+ g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button));
+
+ priv = gsm_color_button_get_instance_private (color_button);
+ priv->type = type;
+
+ gtk_widget_queue_draw (GTK_WIDGET (color_button));
+
+ g_object_notify (G_OBJECT (color_button), "type");
+}
+
+gdouble
+gsm_color_button_get_fraction (GsmColorButton * color_button)
+{
+ GsmColorButtonPrivate *priv;
+
+ g_return_val_if_fail (GSM_IS_COLOR_BUTTON (color_button), 0);
+
+ priv = gsm_color_button_get_instance_private (color_button);
+ return priv->fraction;
+}
+
+void
+gsm_color_button_set_fraction (GsmColorButton * color_button,
+ gdouble fraction)
+{
+ GsmColorButtonPrivate *priv;
+
+ g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button));
+
+ priv = gsm_color_button_get_instance_private (color_button);
+
+ priv->fraction = fraction;
+
+ gtk_widget_queue_draw (GTK_WIDGET (color_button));
+
+ g_object_notify (G_OBJECT (color_button), "fraction");
+}
+
+void
+gsm_color_button_get_color (GsmColorButton * color_button, GdkRGBA * color)
+{
+ GsmColorButtonPrivate *priv;
+
+ g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button));
+
+ priv = gsm_color_button_get_instance_private (color_button);
+
+ color->red = priv->color.red;
+ color->green = priv->color.green;
+ color->blue = priv->color.blue;
+ color->alpha = priv->color.alpha;
+}
+
+void
+gsm_color_button_set_color (GsmColorButton * color_button,
+ const GdkRGBA * color)
+{
+ GsmColorButtonPrivate *priv;
+
+ g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button));
+ g_return_if_fail (color != NULL);
+
+ priv = gsm_color_button_get_instance_private (color_button);
+
+ priv->color.red = color->red;
+ priv->color.green = color->green;
+ priv->color.blue = color->blue;
+ priv->color.alpha = color->alpha;
+
+ gtk_widget_queue_draw (GTK_WIDGET (color_button));
+
+ g_object_notify (G_OBJECT (color_button), "color");
+}
+
+void
+gsm_color_button_set_title (GsmColorButton * color_button,
+ const gchar * title)
+{
+ GsmColorButtonPrivate *priv;
+ gchar *old_title;
+
+ g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button));
+
+ priv = gsm_color_button_get_instance_private (color_button);
+
+ old_title = priv->title;
+ priv->title = g_strdup (title);
+ g_free (old_title);
+
+ if (priv->cc_dialog)
+ gtk_window_set_title (GTK_WINDOW (priv->cc_dialog),
+ priv->title);
+
+ g_object_notify (G_OBJECT (color_button), "title");
+}
+
+gchar *
+gsm_color_button_get_title (GsmColorButton * color_button)
+{
+ GsmColorButtonPrivate *priv;
+
+ g_return_val_if_fail (GSM_IS_COLOR_BUTTON (color_button), NULL);
+
+ priv = gsm_color_button_get_instance_private (color_button);
+ return priv->title;
+}
+
+static void
+gsm_color_button_set_property (GObject * object,
+ guint param_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GsmColorButton *color_button = GSM_COLOR_BUTTON (object);
+
+ switch (param_id)
+ {
+ case PROP_PERCENTAGE:
+ gsm_color_button_set_fraction (color_button,
+ g_value_get_double (value));
+ break;
+ case PROP_TITLE:
+ gsm_color_button_set_title (color_button, g_value_get_string (value));
+ break;
+ case PROP_COLOR:
+ gsm_color_button_set_color (color_button, g_value_get_boxed (value));
+ break;
+ case PROP_TYPE:
+ gsm_color_button_set_cbtype (color_button, g_value_get_uint (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gsm_color_button_get_property (GObject * object,
+ guint param_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GsmColorButton *color_button = GSM_COLOR_BUTTON (object);
+ GdkRGBA color;
+
+ switch (param_id)
+ {
+ case PROP_PERCENTAGE:
+ g_value_set_double (value,
+ gsm_color_button_get_fraction (color_button));
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, gsm_color_button_get_title (color_button));
+ break;
+ case PROP_COLOR:
+ gsm_color_button_get_color (color_button, &color);
+ g_value_set_boxed (value, &color);
+ break;
+ case PROP_TYPE:
+ g_value_set_uint (value, gsm_color_button_get_cbtype (color_button));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
diff --git a/src/legacy/gsm_color_button.h b/src/legacy/gsm_color_button.h
new file mode 100644
index 0000000..385dd14
--- /dev/null
+++ b/src/legacy/gsm_color_button.h
@@ -0,0 +1,77 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Gnome system monitor colour pickers
+ * Copyright (C) 2007 Karl Lattimer <karl@qdh.org.uk>
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with the software; see the file COPYING. If not,
+ * see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GSM_COLOR_BUTTON_H_
+#define _GSM_COLOR_BUTTON_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* The GtkColorSelectionButton widget is a simple color picker in a button.
+ * The button displays a sample of the currently selected color. When
+ * the user clicks on the button, a color selection dialog pops up.
+ * The color picker emits the "color_set" signal when the color is set.
+ */
+#define GSM_TYPE_COLOR_BUTTON (gsm_color_button_get_type ())
+#define GSM_COLOR_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_COLOR_BUTTON, GsmColorButton))
+#define GSM_COLOR_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_COLOR_BUTTON, GsmColorButtonClass))
+#define GSM_IS_COLOR_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_COLOR_BUTTON))
+#define GSM_IS_COLOR_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_COLOR_BUTTON))
+#define GSM_COLOR_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_COLOR_BUTTON, GsmColorButtonClass))
+
+typedef struct _GsmColorButton GsmColorButton;
+typedef struct _GsmColorButtonClass GsmColorButtonClass;
+
+struct _GsmColorButton
+{
+ GtkDrawingArea parent_instance;
+};
+
+/* Widget types */
+enum
+{
+ GSMCP_TYPE_CPU,
+ GSMCP_TYPE_PIE,
+ GSMCP_TYPE_NETWORK_IN,
+ GSMCP_TYPE_NETWORK_OUT,
+ GSMCP_TYPES
+};
+
+struct _GsmColorButtonClass
+{
+ GtkDrawingAreaClass parent_class;
+};
+
+GType gsm_color_button_get_type (void);
+GsmColorButton * gsm_color_button_new (const GdkRGBA * color, guint type);
+void gsm_color_button_set_color (GsmColorButton * color_button, const GdkRGBA * color);
+void gsm_color_button_set_fraction (GsmColorButton * color_button, const gdouble fraction);
+void gsm_color_button_set_cbtype (GsmColorButton * color_button, guint type);
+void gsm_color_button_get_color (GsmColorButton * color_button, GdkRGBA * color);
+gdouble gsm_color_button_get_fraction (GsmColorButton * color_button);
+guint gsm_color_button_get_cbtype (GsmColorButton * color_button);
+void gsm_color_button_set_title (GsmColorButton * color_button, const gchar * title);
+gchar * gsm_color_button_get_title (GsmColorButton * color_button);
+
+G_END_DECLS
+
+#endif /* _GSM_COLOR_BUTTON_H_ */
diff --git a/src/legacy/meson.build b/src/legacy/meson.build
new file mode 100644
index 0000000..a66c863
--- /dev/null
+++ b/src/legacy/meson.build
@@ -0,0 +1,16 @@
+
+libgsm_legacy_sources = [
+ 'e_date.c',
+ 'gsm_color_button.c',
+ 'treeview.c',
+]
+
+libgsm_legacy = static_library('gsm_legacy',
+ libgsm_legacy_sources,
+ include_directories: rootInclude,
+ dependencies: [
+ glib,
+ gtk3,
+ librsvg,
+ ],
+)
diff --git a/src/legacy/treeview.c b/src/legacy/treeview.c
new file mode 100644
index 0000000..b50393d
--- /dev/null
+++ b/src/legacy/treeview.c
@@ -0,0 +1,289 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include "treeview.h"
+
+typedef struct
+{
+ GSettings *settings;
+ gboolean store_column_order;
+ GHashTable *excluded_columns;
+} GsmTreeViewPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsmTreeView, gsm_tree_view, GTK_TYPE_TREE_VIEW)
+
+static void
+gsm_tree_view_finalize (GObject *object)
+{
+ GsmTreeViewPrivate *priv = gsm_tree_view_get_instance_private (GSM_TREE_VIEW (object));
+
+ g_hash_table_destroy (priv->excluded_columns);
+ priv->excluded_columns = NULL;
+
+ G_OBJECT_CLASS (gsm_tree_view_parent_class)->finalize (object);
+}
+
+static void
+gsm_tree_view_class_init (GsmTreeViewClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gsm_tree_view_finalize;
+}
+
+static void
+gsm_tree_view_init (GsmTreeView *self)
+{
+ GsmTreeViewPrivate *priv = gsm_tree_view_get_instance_private (self);
+
+ priv->excluded_columns = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+void
+gsm_tree_view_save_state (GsmTreeView *tree_view)
+{
+ GsmTreeViewPrivate *priv;
+
+ g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
+
+ priv = gsm_tree_view_get_instance_private (tree_view);
+ GtkTreeModel *model;
+ gint sort_col;
+ GtkSortType sort_type;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+ g_settings_delay (priv->settings);
+ if (gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model),
+ &sort_col,
+ &sort_type)) {
+ g_settings_set_int (priv->settings, "sort-col", sort_col);
+ g_settings_set_int (priv->settings, "sort-order", sort_type);
+ }
+
+ if (priv->store_column_order) {
+ GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree_view));
+ GList *iter;
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+
+ for (iter = columns; iter != NULL; iter = iter->next) {
+ gint id = gtk_tree_view_column_get_sort_column_id (GTK_TREE_VIEW_COLUMN (iter->data));
+ g_variant_builder_add (&builder, "i", id);
+ }
+
+ g_settings_set_value (priv->settings, "columns-order",
+ g_variant_builder_end (&builder));
+
+ g_list_free (columns);
+ }
+
+ g_settings_apply (priv->settings);
+}
+
+GtkTreeViewColumn *
+gsm_tree_view_get_column_from_id (GsmTreeView *tree_view, gint sort_id)
+{
+ GList *columns;
+ GList *iter;
+ GtkTreeViewColumn *col = NULL;
+
+ g_return_val_if_fail (GSM_IS_TREE_VIEW (tree_view), NULL);
+
+ columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree_view));
+
+ for (iter = columns; iter != NULL; iter = iter->next) {
+ col = GTK_TREE_VIEW_COLUMN (iter->data);
+ if (gtk_tree_view_column_get_sort_column_id (col) == sort_id)
+ break;
+ }
+
+ g_list_free (columns);
+
+ return col;
+}
+
+static gboolean
+cb_column_header_clicked (GtkTreeViewColumn *column, GdkEventButton *event, gpointer data)
+{
+ GtkMenu *menu = GTK_MENU (data);
+
+ if (event->button == GDK_BUTTON_SECONDARY) {
+ gtk_menu_popup_at_pointer (menu, (GdkEvent*)event);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gsm_tree_view_load_state (GsmTreeView *tree_view)
+{
+ GsmTreeViewPrivate *priv;
+ GtkTreeModel *model;
+ gint sort_col;
+ GtkSortType sort_type;
+
+ g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
+
+ priv = gsm_tree_view_get_instance_private (tree_view);
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+
+ sort_col = g_settings_get_int (priv->settings, "sort-col");
+ sort_type = g_settings_get_int (priv->settings, "sort-order");
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+ sort_col,
+ sort_type);
+
+ if (priv->store_column_order) {
+ GtkMenu *header_menu = GTK_MENU (gtk_menu_new ());
+ GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree_view));
+ GList *iter;
+ GVariantIter *var_iter;
+ GtkTreeViewColumn *col, *last;
+ gint sort_id;
+
+ for (iter = columns; iter != NULL; iter = iter->next) {
+ const char *title;
+ char *key;
+ GtkButton *button;
+ GtkCheckMenuItem *column_item;
+
+ col = GTK_TREE_VIEW_COLUMN (iter->data);
+ sort_id = gtk_tree_view_column_get_sort_column_id (col);
+
+ if (priv->excluded_columns &&
+ g_hash_table_contains (priv->excluded_columns, GINT_TO_POINTER (sort_id))) {
+ gtk_tree_view_column_set_visible (col, FALSE);
+ continue;
+ }
+
+ title = gtk_tree_view_column_get_title (col);
+
+ button = GTK_BUTTON (gtk_tree_view_column_get_button (col));
+ g_signal_connect (button, "button-press-event",
+ G_CALLBACK (cb_column_header_clicked),
+ header_menu);
+
+ column_item = GTK_CHECK_MENU_ITEM (gtk_check_menu_item_new_with_label (title));
+ g_object_bind_property (col, "visible",
+ column_item, "active",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (header_menu), GTK_WIDGET (column_item));
+
+ key = g_strdup_printf ("col-%d-width", sort_id);
+ gtk_tree_view_column_set_fixed_width (col, g_settings_get_int (priv->settings, key));
+ gtk_tree_view_column_set_min_width (col, 30);
+ g_free (key);
+
+ key = g_strdup_printf ("col-%d-visible", sort_id);
+ gtk_tree_view_column_set_visible (col, g_settings_get_boolean (priv->settings, key));
+ g_free (key);
+ }
+
+ g_list_free (columns);
+
+ gtk_widget_show_all (GTK_WIDGET (header_menu));
+
+ g_settings_get (priv->settings, "columns-order", "ai", &var_iter);
+ last = NULL;
+ while (g_variant_iter_loop (var_iter, "i", &sort_id)) {
+ col = gsm_tree_view_get_column_from_id (tree_view, sort_id);
+
+ if (col != NULL && col != last) {
+ gtk_tree_view_move_column_after (GTK_TREE_VIEW (tree_view),
+ col, last);
+ last = col;
+ }
+ }
+ g_variant_iter_free (var_iter);
+ }
+}
+
+void
+gsm_tree_view_add_excluded_column (GsmTreeView *tree_view, gint column_id)
+{
+ GsmTreeViewPrivate *priv;
+
+ g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
+
+ priv = gsm_tree_view_get_instance_private (tree_view);
+ g_hash_table_add (priv->excluded_columns, GINT_TO_POINTER (column_id));
+}
+
+static guint timeout_id = 0;
+static GtkTreeViewColumn *current_column;
+
+static gboolean
+save_column_state (gpointer data)
+{
+ GSettings *settings = G_SETTINGS (data);
+ gint column_id = gtk_tree_view_column_get_sort_column_id (current_column);
+ gint width = gtk_tree_view_column_get_width (current_column);
+ gboolean visible = gtk_tree_view_column_get_visible (current_column);
+
+ gchar *key;
+ g_settings_delay (settings);
+
+ key = g_strdup_printf ("col-%d-width", column_id);
+ g_settings_set_int (settings, key, width);
+ g_free (key);
+
+ key = g_strdup_printf ("col-%d-visible", column_id);
+ g_settings_set_boolean (settings, key, visible);
+ g_free (key);
+ timeout_id = 0;
+ g_settings_apply (settings);
+
+ return FALSE;
+}
+
+static void
+cb_update_column_state (GObject *object, GParamSpec *pspec, gpointer data)
+{
+ GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN (object);
+
+ current_column = column;
+
+ if (timeout_id > 0)
+ g_source_remove (timeout_id);
+
+ timeout_id = g_timeout_add_seconds (1, save_column_state, data);
+}
+
+void
+gsm_tree_view_append_and_bind_column (GsmTreeView *tree_view, GtkTreeViewColumn *column)
+{
+ GsmTreeViewPrivate *priv;
+
+ g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
+ g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (column));
+
+ priv = gsm_tree_view_get_instance_private (tree_view);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
+ column);
+
+ g_signal_connect (column, "notify::fixed-width",
+ G_CALLBACK (cb_update_column_state),
+ priv->settings);
+
+ g_signal_connect (column, "notify::visible",
+ G_CALLBACK (cb_update_column_state),
+ priv->settings);
+}
+
+
+GsmTreeView *
+gsm_tree_view_new (GSettings *settings, gboolean store_column_order)
+{
+ GsmTreeView *self = g_object_new (GSM_TYPE_TREE_VIEW, NULL);
+ GsmTreeViewPrivate *priv = gsm_tree_view_get_instance_private (self);
+
+ priv->settings = settings;
+ priv->store_column_order = store_column_order;
+
+ return self;
+}
diff --git a/src/legacy/treeview.h b/src/legacy/treeview.h
new file mode 100644
index 0000000..7223ab1
--- /dev/null
+++ b/src/legacy/treeview.h
@@ -0,0 +1,43 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_TREE_VIEW_H_
+#define _GSM_TREE_VIEW_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_TREE_VIEW (gsm_tree_view_get_type ())
+#define GSM_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_TREE_VIEW, GsmTreeView))
+#define GSM_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_TREE_VIEW, GsmTreeViewClass))
+#define GSM_IS_TREE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_TREE_VIEW))
+#define GSM_IS_TREE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_TREE_VIEW))
+#define GSM_TREE_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_TREE_VIEW, GsmTreeViewClass))
+
+typedef struct _GsmTreeView GsmTreeView;
+typedef struct _GsmTreeViewClass GsmTreeViewClass;
+
+struct _GsmTreeView
+{
+ GtkTreeView parent_instance;
+};
+
+struct _GsmTreeViewClass
+{
+ GtkTreeViewClass parent_class;
+};
+
+GType gsm_tree_view_get_type (void) G_GNUC_CONST;
+GsmTreeView * gsm_tree_view_new (GSettings *settings,
+ gboolean store_column_order);
+void gsm_tree_view_save_state (GsmTreeView *tree_view);
+void gsm_tree_view_load_state (GsmTreeView *tree_view);
+GtkTreeViewColumn * gsm_tree_view_get_column_from_id (GsmTreeView *tree_view,
+ gint sort_id);
+void gsm_tree_view_add_excluded_column (GsmTreeView *tree_view,
+ gint column_id);
+void gsm_tree_view_append_and_bind_column (GsmTreeView *tree_view,
+ GtkTreeViewColumn *column);
+
+G_END_DECLS
+
+#endif /* _GSM_TREE_VIEW_H_ */
diff --git a/src/load-graph.cpp b/src/load-graph.cpp
new file mode 100644
index 0000000..377623f
--- /dev/null
+++ b/src/load-graph.cpp
@@ -0,0 +1,1119 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <math.h>
+
+#include <glib/gi18n.h>
+
+#include <glibtop.h>
+#include <glibtop/cpu.h>
+#include <glibtop/mem.h>
+#include <glibtop/swap.h>
+#include <glibtop/netload.h>
+#include <glibtop/netlist.h>
+
+#include "application.h"
+#include "load-graph.h"
+#include "util.h"
+#include "legacy/gsm_color_button.h"
+
+gchar* format_duration(unsigned seconds);
+
+void LoadGraph::clear_background()
+{
+ if (background) {
+ cairo_surface_destroy (background);
+ background = NULL;
+ }
+}
+
+bool LoadGraph::is_logarithmic_scale() const
+{
+ // logarithmic scale is used only for memory graph
+ return this->type == LOAD_GRAPH_MEM && GsmApplication::get()->config.logarithmic_scale;
+}
+
+unsigned LoadGraph::num_bars() const
+{
+ unsigned n;
+
+ // keep 100 % num_bars == 0
+ switch (static_cast<int>(draw_height / (fontsize + 14)))
+ {
+ case 0:
+ case 1:
+ n = 1;
+ break;
+ case 2:
+ case 3:
+ n = 2;
+ break;
+ case 4:
+ n = 4;
+ break;
+ case 5:
+ n = 5;
+ if (this->is_logarithmic_scale())
+ n = 4;
+ break;
+ default:
+ n = 5;
+ if (this->is_logarithmic_scale())
+ n = 6;
+ }
+
+ return n;
+}
+
+/*
+ Returns Y scale caption based on give index of the label.
+ Takes into account whether the scale should be logarithmic for memory graph.
+ */
+char* LoadGraph::get_caption(guint index)
+{
+ char *caption;
+ unsigned num_bars = this->num_bars();
+ guint64 max_value;
+ if (this->type == LOAD_GRAPH_NET)
+ max_value = this->net.max;
+ else
+ max_value = 100;
+
+ // operation orders matters so it's 0 if index == num_bars
+ float caption_percentage = (float)max_value - index * (float)max_value / num_bars;
+
+ if (this->is_logarithmic_scale()) {
+ float caption_value = caption_percentage == 0 ? 0 : pow(100, caption_percentage / max_value);
+ // Translators: loadgraphs y axis percentage labels: 0 %, 50%, 100%
+ caption = g_strdup_printf(_("%.0f %%"), caption_value);
+ } else if (this->type == LOAD_GRAPH_NET) {
+ const std::string captionstr(procman::format_network_rate((guint64)caption_percentage));
+ caption = g_strdup(captionstr.c_str());
+ } else {
+ // Translators: loadgraphs y axis percentage labels: 0 %, 50%, 100%
+ caption = g_strdup_printf(_("%.0f %%"), caption_percentage);
+ }
+
+ return caption;
+}
+
+/*
+ Translates y partial position to logarithmic position if set to logarithmic scale.
+*/
+float LoadGraph::translate_to_log_partial_if_needed(float position_partial)
+{
+ if (this->is_logarithmic_scale())
+ position_partial = position_partial == 0 ? 0 : log10(position_partial * 100) / 2;
+
+ return position_partial;
+}
+
+gchar* format_duration(unsigned seconds) {
+ gchar* caption = NULL;
+
+ unsigned minutes = seconds / 60;
+ unsigned hours = seconds / 3600;
+
+ if (hours != 0) {
+ if (minutes % 60 == 0) {
+ // If minutes mod 60 is 0 set it to 0, to prevent it from showing full hours in
+ // minutes in addition to hours.
+ minutes = 0;
+ } else {
+ // Round minutes as seconds wont get shown if neither hours nor minutes are 0.
+ minutes = int(rint(seconds / 60.0)) % 60;
+ if (minutes == 0) {
+ // Increase hours if rounding minutes results in 0, because that would be
+ // what it would be rounded to.
+ hours++;
+ // Set seconds to hours * 3600 to prevent seconds from being drawn.
+ seconds = hours * 3600;
+ }
+ }
+
+ }
+
+ gchar* captionH = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u hr", "%u hrs", hours), hours);
+ gchar* captionM = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u min", "%u mins", minutes),
+ minutes);
+ gchar* captionS = g_strdup_printf(dngettext(GETTEXT_PACKAGE, "%u sec", "%u secs", seconds % 60),
+ seconds % 60);
+
+ caption = g_strjoin (" ", hours > 0 ? captionH : "",
+ minutes > 0 ? captionM : "",
+ seconds % 60 > 0 ? captionS : "",
+ NULL);
+ g_free (captionH);
+ g_free (captionM);
+ g_free (captionS);
+
+ return caption;
+}
+
+const int FRAME_WIDTH = 4;
+static void draw_background(LoadGraph *graph) {
+ GtkAllocation allocation;
+ cairo_t *cr;
+ guint i;
+ double label_x_offset_modifier, label_y_offset_modifier;
+ unsigned num_bars;
+ gchar *caption;
+ PangoLayout* layout;
+ PangoAttrList *attrs = NULL;
+ PangoFontDescription* font_desc;
+ PangoRectangle extents;
+ cairo_surface_t *surface;
+ GdkRGBA fg;
+ GdkRGBA fg_grid;
+ double const border_alpha = 0.7;
+ double const grid_alpha = border_alpha / 2.0;
+
+ num_bars = graph->num_bars();
+ graph->graph_dely = (graph->draw_height - 15) / num_bars; /* round to int to avoid AA blur */
+ graph->real_draw_height = graph->graph_dely * num_bars;
+ graph->graph_delx = (graph->draw_width - 2.0 - graph->indent) / (graph->num_points - 3);
+ graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH;
+
+ gtk_widget_get_allocation (GTK_WIDGET (graph->disp), &allocation);
+ surface = gdk_window_create_similar_surface (gtk_widget_get_window (GTK_WIDGET (graph->disp)),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ allocation.width,
+ allocation.height);
+ cr = cairo_create (surface);
+
+ GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (GsmApplication::get()->stack));
+
+ gtk_style_context_get_color (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), &fg);
+
+ cairo_paint_with_alpha (cr, 0.0);
+ layout = pango_cairo_create_layout (cr);
+
+ attrs = make_tnum_attr_list ();
+ pango_layout_set_attributes (layout, attrs);
+ g_clear_pointer (&attrs, pango_attr_list_unref);
+
+ gtk_style_context_get (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), GTK_STYLE_PROPERTY_FONT, &font_desc, NULL);
+ pango_font_description_set_size (font_desc, 0.8 * graph->fontsize * PANGO_SCALE);
+ pango_layout_set_font_description (layout, font_desc);
+ pango_font_description_free (font_desc);
+
+ /* draw frame */
+ cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH);
+
+ /* Draw background rectangle */
+ /* When a user uses a dark theme, the hard-coded
+ * white background in GSM is a lone white on the
+ * display, which makes the user unhappy. To fix
+ * this, here we offer the user a chance to set
+ * his favorite background color. */
+ gtk_style_context_save (context);
+
+ /* Here we specify the name of the class. Now in
+ * the theme's CSS we can specify the own colors
+ * for this class. */
+ gtk_style_context_add_class (context, "loadgraph");
+
+ /* And in case the user does not care, we add
+ * classes that usually have a white background. */
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_PAPER);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
+
+ /* And, as a bonus, the user can choose the color of the grid. */
+ gtk_style_context_get_color (context, gtk_widget_get_state_flags (GTK_WIDGET (GsmApplication::get()->stack)), &fg_grid);
+
+ /* Why not use the new features of the
+ * GTK instead of cairo_rectangle ?! :) */
+ gtk_render_background (context, cr, graph->indent, 0.0,
+ graph->draw_width - graph->rmargin - graph->indent,
+ graph->real_draw_height);
+
+ gtk_style_context_restore (context);
+
+ cairo_set_line_width (cr, 1.0);
+
+ for (i = 0; i <= num_bars; ++i) {
+ double y;
+
+ if (i == 0)
+ y = 0.5 + graph->fontsize / 2.0;
+ else if (i == num_bars)
+ y = i * graph->graph_dely + 0.5;
+ else
+ y = i * graph->graph_dely + graph->fontsize / 2.0;
+
+ gdk_cairo_set_source_rgba (cr, &fg);
+ caption = graph->get_caption(i);
+ pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
+ pango_layout_set_text (layout, caption, -1);
+ pango_layout_get_extents (layout, NULL, &extents);
+ label_y_offset_modifier = i == 0 ? 0.5
+ : i == num_bars
+ ? 1.0
+ : 0.85;
+ cairo_move_to (cr, graph->draw_width - graph->indent - 23,
+ y - label_y_offset_modifier * extents.height / PANGO_SCALE);
+ pango_cairo_show_layout (cr, layout);
+ g_free(caption);
+
+ if (i==0 || i==num_bars)
+ fg_grid.alpha = border_alpha;
+ else
+ fg_grid.alpha = grid_alpha;
+
+ gdk_cairo_set_source_rgba (cr, &fg_grid);
+ cairo_move_to (cr, graph->indent, i * graph->graph_dely + 0.5);
+ cairo_line_to (cr, graph->draw_width - graph->rmargin + 0.5 + 4, i * graph->graph_dely + 0.5);
+ cairo_stroke (cr);
+ }
+
+
+ const unsigned total_seconds = graph->speed * (graph->num_points - 2) / 1000 * graph->frames_per_unit;
+
+ for (unsigned int i = 0; i < 7; i++) {
+ double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6;
+
+ if (i==0 || i==6)
+ fg_grid.alpha = border_alpha;
+ else
+ fg_grid.alpha = grid_alpha;
+
+ gdk_cairo_set_source_rgba (cr, &fg_grid);
+ cairo_move_to (cr, (ceil(x) + 0.5) + graph->indent, 0.5);
+ cairo_line_to (cr, (ceil(x) + 0.5) + graph->indent, graph->real_draw_height + 4.5);
+ cairo_stroke(cr);
+
+ caption = format_duration(total_seconds - i * total_seconds / 6);
+
+ pango_layout_set_text (layout, caption, -1);
+ pango_layout_get_extents (layout, NULL, &extents);
+ label_x_offset_modifier = i == 0 ? 0
+ : i == 6
+ ? 1.0
+ : 0.5;
+ cairo_move_to (cr,
+ (ceil(x) + 0.5 + graph->indent) - label_x_offset_modifier * extents.width / PANGO_SCALE + 1.0,
+ graph->draw_height - 1.0 * extents.height / PANGO_SCALE);
+ gdk_cairo_set_source_rgba (cr, &fg);
+ pango_cairo_show_layout (cr, layout);
+ g_free (caption);
+ }
+ g_object_unref(layout);
+ cairo_stroke (cr);
+ cairo_destroy (cr);
+ graph->background = surface;
+}
+
+/* Redraws the backing buffer for the load graph and updates the window */
+void
+load_graph_queue_draw (LoadGraph *graph)
+{
+ /* repaint */
+ gtk_widget_queue_draw (GTK_WIDGET (graph->disp));
+}
+
+void load_graph_update_data (LoadGraph *graph);
+static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;)
+
+static void
+load_graph_rescale (LoadGraph *graph) {
+ ///org/gnome/desktop/interface/text-scaling-factor
+ graph->fontsize = 8 * graph->font_settings->get_double ("text-scaling-factor");
+ graph->clear_background();
+
+ load_graph_queue_draw (graph);
+}
+
+static gboolean
+load_graph_configure (GtkWidget *widget,
+ GdkEventConfigure *event,
+ gpointer data_ptr)
+{
+ GtkAllocation allocation;
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+
+ load_graph_rescale (graph);
+
+ gtk_widget_get_allocation (widget, &allocation);
+ graph->draw_width = allocation.width - 2 * FRAME_WIDTH;
+ graph->draw_height = allocation.height - 2 * FRAME_WIDTH;
+
+ graph->clear_background();
+
+ load_graph_queue_draw (graph);
+
+ return TRUE;
+}
+
+static void force_refresh (LoadGraph * const graph)
+{
+ graph->clear_background();
+ load_graph_queue_draw (graph);
+}
+
+static void
+load_graph_style_updated (GtkWidget *widget,
+ gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+ force_refresh (graph);
+}
+
+static gboolean
+load_graph_state_changed (GtkWidget *widget,
+ GtkStateFlags *flags,
+ gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+ force_refresh (graph);
+ graph->draw = gtk_widget_is_visible (widget);
+ return TRUE;
+}
+
+static gboolean
+load_graph_draw (GtkWidget *widget,
+ cairo_t * cr,
+ gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+
+ guint i;
+ gint j;
+ gdouble sample_width, x_offset;
+
+ /* Number of pixels wide for one sample point */
+ sample_width = (double)(graph->draw_width - graph->rmargin - graph->indent) / (double)graph->num_points;
+ /* Lines start at the right edge of the drawing,
+ * a bit outside the clip rectangle. */
+ x_offset = graph->draw_width - graph->rmargin + sample_width + 2;
+ /* Adjustment for smooth movement between samples */
+ x_offset -= sample_width * graph->render_counter / (double)graph->frames_per_unit;
+
+ /* draw the graph */
+
+ if (graph->background == NULL) {
+ draw_background(graph);
+ }
+ cairo_set_source_surface (cr, graph->background, 0, 0);
+ cairo_paint (cr);
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
+ cairo_rectangle (cr, graph->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1,
+ graph->draw_width - graph->rmargin - graph->indent - 1,
+ graph->real_draw_height + FRAME_WIDTH - 1);
+ cairo_clip(cr);
+
+ bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;
+ bool drawSmooth = GsmApplication::get()->config.draw_smooth;
+ for (j = graph->n-1; j >= 0; j--) {
+ gdk_cairo_set_source_rgba (cr, &(graph->colors [j]));
+ // Start drawing on the right at the correct height.
+ cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height + 3);
+ // then draw the path of the line.
+ // Loop starts at 1 because the curve accesses the 0th data point.
+ for (i = 1; i < graph->num_points; ++i) {
+ if (graph->data[i][j] == -1.0f)
+ continue;
+ if (drawSmooth) {
+ cairo_curve_to (cr,
+ x_offset - ((i - 0.5f) * graph->graph_delx),
+ (1.0 - graph->data[i-1][j]) * graph->real_draw_height + 3,
+ x_offset - ((i - 0.5f) * graph->graph_delx),
+ (1.0 - graph->data[i][j]) * graph->real_draw_height + 3,
+ x_offset - (i * graph->graph_delx),
+ (1.0 - graph->data[i][j]) * graph->real_draw_height + 3);
+ } else {
+ cairo_line_to (cr, x_offset - (i * graph->graph_delx),
+ (1.0 - graph->data[i][j]) * graph->real_draw_height + 3);
+ }
+
+ }
+ if (drawStacked) {
+ // Draw the remaining outline of the area:
+ // Left bottom corner
+ cairo_rel_line_to (cr, 0, graph->real_draw_height + 3);
+ // Right bottom corner. It's drawn far outside the visible area
+ // to avoid a weird bug where it's not filling the area it should completely.
+ cairo_rel_line_to (cr, x_offset * 2, 0);
+
+ //cairo_stroke_preserve(cr);
+ cairo_close_path(cr);
+ cairo_fill(cr);
+ } else {
+ cairo_stroke (cr);
+ }
+ }
+
+ return TRUE;
+}
+
+void
+load_graph_reset (LoadGraph *graph)
+{
+ std::fill(graph->data_block.begin(), graph->data_block.end(), -1.0);
+}
+
+static void
+get_load (LoadGraph *graph)
+{
+ guint i;
+ glibtop_cpu cpu;
+
+ glibtop_get_cpu (&cpu);
+
+ auto NOW = [&]() -> guint64 (&)[GLIBTOP_NCPU][N_CPU_STATES] { return graph->cpu.times[graph->cpu.now]; };
+ auto LAST = [&]() -> guint64 (&)[GLIBTOP_NCPU][N_CPU_STATES] { return graph->cpu.times[graph->cpu.now ^ 1]; };
+
+ if (graph->n == 1) {
+ NOW()[0][CPU_TOTAL] = cpu.total;
+ NOW()[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys;
+ } else {
+ for (i = 0; i < graph->n; i++) {
+ NOW()[i][CPU_TOTAL] = cpu.xcpu_total[i];
+ NOW()[i][CPU_USED] = cpu.xcpu_user[i] + cpu.xcpu_nice[i]
+ + cpu.xcpu_sys[i];
+ }
+ }
+
+ // on the first call, LAST is 0
+ // which means data is set to the average load since boot
+ // that value has no meaning, we just want all the
+ // graphs to be aligned, so the CPU graph needs to start
+ // immediately
+ bool drawStacked = graph->type == LOAD_GRAPH_CPU && GsmApplication::get()->config.draw_stacked;
+
+ for (i = 0; i < graph->n; i++) {
+ float load;
+ float total, used;
+ gchar *text;
+
+ total = NOW()[i][CPU_TOTAL] - LAST()[i][CPU_TOTAL];
+ used = NOW()[i][CPU_USED] - LAST()[i][CPU_USED];
+
+ load = used / MAX(total, 1.0f);
+ graph->data[0][i] = load;
+ if (drawStacked) {
+ graph->data[0][i] /= graph->n;
+ if (i > 0) {
+ graph->data[0][i] += graph->data[0][i-1];
+ }
+ }
+
+ /* Update label */
+ // Translators: CPU usage percentage label: 95.7%
+ text = g_strdup_printf(_("%.1f%%"), load * 100.0f);
+ gtk_label_set_text(GTK_LABEL(graph->labels.cpu[i]), text);
+ g_free(text);
+ }
+
+ graph->cpu.now ^= 1;
+}
+
+
+namespace
+{
+
+ void set_memory_label_and_picker(GtkLabel* label, GsmColorButton* picker,
+ guint64 used, guint64 cached, guint64 total, double percent)
+ {
+ char* used_text;
+ char* cached_text;
+ char* cached_label;
+ char* total_text;
+ char* text;
+
+ used_text = format_byte_size(used, GsmApplication::get()->config.resources_memory_in_iec);
+ cached_text = format_byte_size(cached, GsmApplication::get()->config.resources_memory_in_iec);
+ total_text = format_byte_size(total, GsmApplication::get()->config.resources_memory_in_iec);
+ if (total == 0) {
+ text = g_strdup(_("not available"));
+ } else {
+ // xgettext: "540MiB (53 %) of 1.0 GiB" or "540MB (53 %) of 1.0 GB"
+ text = g_strdup_printf(_("%s (%.1f%%) of %s"), used_text, 100.0 * percent, total_text);
+
+ if (cached != 0) {
+ // xgettext: Used cache string, e.g.: "Cache 2.4GiB" or "Cache 2.4GB"
+ cached_label = g_strdup_printf(_("Cache %s"), cached_text);
+ text = g_strdup_printf("%s\n%s", text, cached_label);
+ g_free (cached_label);
+ }
+ }
+ gtk_label_set_text(label, text);
+ g_free(used_text);
+ g_free(cached_text);
+ g_free(total_text);
+ g_free(text);
+
+ if (picker)
+ gsm_color_button_set_fraction(picker, percent);
+ }
+}
+
+static void
+get_memory (LoadGraph *graph)
+{
+ float mempercent, swappercent;
+
+ glibtop_mem mem;
+ glibtop_swap swap;
+
+ glibtop_get_mem (&mem);
+ glibtop_get_swap (&swap);
+
+ /* There's no swap on LiveCD : 0.0f is better than NaN :) */
+ swappercent = (swap.total ? (float)swap.used / (float)swap.total : 0.0f);
+ mempercent = (float)mem.user / (float)mem.total;
+ set_memory_label_and_picker(GTK_LABEL(graph->labels.memory),
+ GSM_COLOR_BUTTON(graph->mem_color_picker),
+ mem.user, mem.cached, mem.total, mempercent);
+
+ set_memory_label_and_picker(GTK_LABEL(graph->labels.swap),
+ GSM_COLOR_BUTTON(graph->swap_color_picker),
+ swap.used, 0, swap.total, swappercent);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (graph->swap_color_picker), swap.total > 0);
+
+ graph->data[0][0] = graph->translate_to_log_partial_if_needed(mempercent);
+ graph->data[0][1] = swap.total>0 ? graph->translate_to_log_partial_if_needed(swappercent) : -1.0;
+}
+
+/* Nice Numbers for Graph Labels after Paul Heckbert
+ nicenum: find a "nice" number approximately equal to x.
+ Round the number if round=1, take ceiling if round=0 */
+
+static double
+nicenum (double x, int round)
+{
+ int expv; /* exponent of x */
+ double f; /* fractional part of x */
+ double nf; /* nice, rounded fraction */
+
+ expv = floor(log10(x));
+ f = x/pow(10.0, expv); /* between 1 and 10 */
+ if (round) {
+ if (f < 1.5)
+ nf = 1.0;
+ else if (f < 3.0)
+ nf = 2.0;
+ else if (f < 7.0)
+ nf = 5.0;
+ else
+ nf = 10.0;
+ } else {
+ if (f <= 1.0)
+ nf = 1.0;
+ else if (f <= 2.0)
+ nf = 2.0;
+ else if (f <= 5.0)
+ nf = 5.0;
+ else
+ nf = 10.0;
+ }
+ return nf * pow(10.0, expv);
+}
+
+static void
+net_scale (LoadGraph *graph, guint64 din, guint64 dout)
+{
+ graph->data[0][0] = 1.0f * din / graph->net.max;
+ graph->data[0][1] = 1.0f * dout / graph->net.max;
+
+ guint64 dmax = std::max(din, dout);
+ if (graph->latest == 0) {
+ graph->net.values[graph->num_points - 1] = dmax;
+ } else {
+ graph->net.values[graph->latest - 1] = dmax;
+ }
+
+ guint64 new_max;
+ // both way, new_max is the greatest value
+ if (dmax >= graph->net.max)
+ new_max = dmax;
+ else
+ new_max = *std::max_element(&graph->net.values[0],
+ &graph->net.values[graph->num_points - 1]);
+
+ //
+ // Round network maximum
+ //
+
+ const guint64 bak_max(new_max);
+
+ if (GsmApplication::get()->config.network_in_bits) {
+ // nice number is for the ticks
+ unsigned ticks = graph->num_bars();
+
+ // gets messy at low values due to division by 8
+ guint64 bit_max = std::max( new_max*8, G_GUINT64_CONSTANT(10000) );
+
+ // our tick size leads to max
+ double d = nicenum(bit_max/ticks, 0);
+ bit_max = ticks * d;
+ new_max = bit_max / 8;
+
+ procman_debug("bak*8 %" G_GUINT64_FORMAT ", ticks %d, d %f"
+ ", bit_max %" G_GUINT64_FORMAT ", new_max %" G_GUINT64_FORMAT,
+ bak_max*8, ticks, d, bit_max, new_max );
+ } else {
+ // round up to get some extra space
+ // yes, it can overflow
+ new_max = 1.1 * new_max;
+ // make sure max is not 0 to avoid / 0
+ // default to 1 KiB
+ new_max = std::max(new_max, G_GUINT64_CONSTANT(1024));
+
+ // decompose new_max = coef10 * 2**(base10 * 10)
+ // where coef10 and base10 are integers and coef10 < 2**10
+ //
+ // e.g: ceil(100.5 KiB) = 101 KiB = 101 * 2**(1 * 10)
+ // where base10 = 1, coef10 = 101, pow2 = 16
+
+ guint64 pow2 = std::floor(log2(new_max));
+ guint64 base10 = pow2 / 10.0;
+ guint64 coef10 = std::ceil(new_max / double(G_GUINT64_CONSTANT(1) << (base10 * 10)));
+ g_assert(new_max <= (coef10 * (G_GUINT64_CONSTANT(1) << (base10 * 10))));
+
+ // then decompose coef10 = x * 10**factor10
+ // where factor10 is integer and x < 10
+ // so we new_max has only 1 significant digit
+
+ guint64 factor10 = std::pow(10.0, std::floor(std::log10(coef10)));
+ coef10 = std::ceil(coef10 / double(factor10)) * factor10;
+
+ new_max = coef10 * (G_GUINT64_CONSTANT(1) << guint64(base10 * 10));
+ procman_debug("bak %" G_GUINT64_FORMAT " new_max %" G_GUINT64_FORMAT
+ "pow2 %" G_GUINT64_FORMAT " coef10 %" G_GUINT64_FORMAT,
+ bak_max, new_max, pow2, coef10);
+ }
+
+ if (bak_max > new_max) {
+ procman_debug("overflow detected: bak=%" G_GUINT64_FORMAT
+ " new=%" G_GUINT64_FORMAT,
+ bak_max, new_max);
+ new_max = bak_max;
+ }
+
+ // if max is the same or has decreased but not so much, don't
+ // do anything to avoid rescaling
+ if ((0.8 * graph->net.max) < new_max && new_max <= graph->net.max)
+ return;
+
+ const double scale = 1.0f * graph->net.max / new_max;
+
+ for (size_t i = 0; i < graph->num_points; i++) {
+ if (graph->data[i][0] >= 0.0f) {
+ graph->data[i][0] *= scale;
+ graph->data[i][1] *= scale;
+ }
+ }
+
+ procman_debug("rescale dmax = %" G_GUINT64_FORMAT
+ " max = %" G_GUINT64_FORMAT
+ " new_max = %" G_GUINT64_FORMAT,
+ dmax, graph->net.max, new_max);
+
+ graph->net.max = new_max;
+
+ // force the graph background to be redrawn now that scale has changed
+ graph->clear_background();
+}
+
+static void
+get_net (LoadGraph *graph)
+{
+ glibtop_netlist netlist;
+ char **ifnames;
+ guint32 i;
+ guint64 in = 0, out = 0;
+ guint64 time;
+ guint64 din, dout;
+ ifnames = glibtop_get_netlist(&netlist);
+
+ for (i = 0; i < netlist.number; ++i)
+ {
+ glibtop_netload netload;
+ glibtop_get_netload (&netload, ifnames[i]);
+
+ if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK))
+ continue;
+
+ /* Skip interfaces without any IPv4/IPv6 address (or
+ those with only a LINK ipv6 addr) However we need to
+ be able to exclude these while still keeping the
+ value so when they get online (with NetworkManager
+ for example) we don't get a sudden peak. Once we're
+ able to get this, ignoring down interfaces will be
+ possible too. */
+ if (not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS6)
+ and netload.scope6 != GLIBTOP_IF_IN6_SCOPE_LINK)
+ and not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS)))
+ continue;
+
+ /* Don't skip interfaces that are down (GLIBTOP_IF_FLAGS_UP)
+ to avoid spikes when they are brought up */
+
+ in += netload.bytes_in;
+ out += netload.bytes_out;
+ }
+
+ g_strfreev(ifnames);
+
+ time = g_get_monotonic_time ();
+
+ if (in >= graph->net.last_in && out >= graph->net.last_out && graph->net.time != 0) {
+ float dtime;
+ dtime = ((double) (time - graph->net.time)) / G_USEC_PER_SEC;
+ din = static_cast<guint64>((in - graph->net.last_in) / dtime);
+ dout = static_cast<guint64>((out - graph->net.last_out) / dtime);
+ } else {
+ /* Don't calc anything if new data is less than old (interface
+ removed, counters reset, ...) or if it is the first time */
+ din = 0;
+ dout = 0;
+ }
+
+ graph->net.last_in = in;
+ graph->net.last_out = out;
+ graph->net.time = time;
+
+ net_scale(graph, din, dout);
+
+ gtk_label_set_text (GTK_LABEL (graph->labels.net_in), procman::format_network_rate(din).c_str());
+ gtk_label_set_text (GTK_LABEL (graph->labels.net_in_total), procman::format_network(in).c_str());
+
+ gtk_label_set_text (GTK_LABEL (graph->labels.net_out), procman::format_network_rate(dout).c_str());
+ gtk_label_set_text (GTK_LABEL (graph->labels.net_out_total), procman::format_network(out).c_str());
+}
+
+
+
+void
+load_graph_update_data (LoadGraph *graph)
+{
+ // Rotate data one element down.
+ std::rotate(graph->data.begin(),
+ graph->data.end() - 1,
+ graph->data.end());
+
+ // Update rotation counter.
+ graph->latest = (graph->latest + 1) % graph->num_points;
+
+ // Replace the 0th element
+ switch (graph->type) {
+ case LOAD_GRAPH_CPU:
+ get_load(graph);
+ break;
+ case LOAD_GRAPH_MEM:
+ get_memory(graph);
+ break;
+ case LOAD_GRAPH_NET:
+ get_net(graph);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+
+
+/* Updates the load graph when the timeout expires */
+static gboolean
+load_graph_update (gpointer user_data)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(user_data);
+
+ if (graph->render_counter == graph->frames_per_unit - 1)
+ load_graph_update_data(graph);
+
+ if (graph->draw)
+ load_graph_queue_draw (graph);
+
+ graph->render_counter++;
+
+ if (graph->render_counter >= graph->frames_per_unit)
+ graph->render_counter = 0;
+
+ return TRUE;
+}
+
+
+
+LoadGraph::~LoadGraph()
+{
+ load_graph_stop(this);
+
+ if (timer_index)
+ g_source_remove(timer_index);
+
+ clear_background();
+}
+
+
+
+static gboolean
+load_graph_destroy (GtkWidget *widget, gpointer data_ptr)
+{
+ LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
+
+ delete graph;
+
+ return FALSE;
+}
+
+
+LoadGraph::LoadGraph(guint type)
+ : fontsize(8.0),
+ rmargin(6 * fontsize),
+ indent(18.0),
+ n(0),
+ type(type),
+ speed(0),
+ num_points(0),
+ latest(0),
+ draw_width(0),
+ draw_height(0),
+ render_counter(0),
+ frames_per_unit(10), // this will be changed but needs initialising
+ graph_dely(0),
+ real_draw_height(0),
+ graph_delx(0.0),
+ graph_buffer_offset(0),
+ colors(),
+ data_block(),
+ data(),
+ main_widget(NULL),
+ disp(NULL),
+ background(NULL),
+ timer_index(0),
+ draw(FALSE),
+ labels(),
+ mem_color_picker(NULL),
+ swap_color_picker(NULL),
+ font_settings(Gio::Settings::create (FONT_SETTINGS_SCHEMA)),
+ cpu(),
+ net()
+{
+ LoadGraph * const graph = this;
+ font_settings->signal_changed(FONT_SETTING_SCALING).connect([this](const Glib::ustring&) { load_graph_rescale (this); } );
+ // FIXME:
+ // on configure, graph->frames_per_unit = graph->draw_width/(LoadGraph::NUM_POINTS);
+ // knock FRAMES down to 5 until cairo gets faster
+
+ switch (type) {
+ case LOAD_GRAPH_CPU:
+ cpu = CPU {};
+ n = GsmApplication::get()->config.num_cpus;
+
+ for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i)
+ labels.cpu[i] = make_tnum_label ();
+
+ break;
+
+ case LOAD_GRAPH_MEM:
+ n = 2;
+ labels.memory = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.memory), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.memory), GTK_ALIGN_START);
+ gtk_widget_show (GTK_WIDGET (labels.memory));
+ labels.swap = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.swap), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.swap), GTK_ALIGN_START);
+ gtk_widget_show (GTK_WIDGET (labels.swap));
+ break;
+
+ case LOAD_GRAPH_NET:
+ net = NET {};
+ n = 2;
+ net.max = 1;
+ labels.net_in = make_tnum_label ();
+ gtk_label_set_width_chars(labels.net_in, 10);
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_in), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_in), GTK_ALIGN_END);
+ gtk_widget_show (GTK_WIDGET (labels.net_in));
+
+ labels.net_in_total = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_in_total), GTK_ALIGN_END);
+ gtk_label_set_width_chars(labels.net_in_total, 10);
+ gtk_widget_show (GTK_WIDGET (labels.net_in_total));
+
+ labels.net_out = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_out), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
+ gtk_label_set_width_chars(labels.net_out, 10);
+ gtk_widget_show (GTK_WIDGET (labels.net_out));
+
+ labels.net_out_total = make_tnum_label ();
+ gtk_widget_set_valign (GTK_WIDGET (labels.net_out_total), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (labels.net_out), GTK_ALIGN_END);
+ gtk_label_set_width_chars(labels.net_out_total, 10);
+ gtk_widget_show (GTK_WIDGET (labels.net_out_total));
+
+ break;
+ }
+
+ speed = GsmApplication::get()->config.graph_update_interval;
+
+ num_points = GsmApplication::get()->config.graph_data_points + 2;
+
+ colors.resize(n);
+
+ switch (type) {
+ case LOAD_GRAPH_CPU:
+ memcpy(&colors[0], GsmApplication::get()->config.cpu_color,
+ n * sizeof colors[0]);
+ break;
+ case LOAD_GRAPH_MEM:
+ colors[0] = GsmApplication::get()->config.mem_color;
+ colors[1] = GsmApplication::get()->config.swap_color;
+ mem_color_picker = gsm_color_button_new (&colors[0],
+ GSMCP_TYPE_PIE);
+ swap_color_picker = gsm_color_button_new (&colors[1],
+ GSMCP_TYPE_PIE);
+ break;
+ case LOAD_GRAPH_NET:
+ net.values = std::vector<unsigned>(num_points);
+ colors[0] = GsmApplication::get()->config.net_in_color;
+ colors[1] = GsmApplication::get()->config.net_out_color;
+ break;
+ }
+
+ timer_index = 0;
+ render_counter = (frames_per_unit - 1);
+ draw = FALSE;
+
+ main_widget = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
+ gtk_widget_set_size_request(GTK_WIDGET (main_widget), -1, LoadGraph::GRAPH_MIN_HEIGHT);
+ gtk_widget_show (GTK_WIDGET (main_widget));
+
+ disp = GTK_DRAWING_AREA (gtk_drawing_area_new ());
+ gtk_widget_show (GTK_WIDGET (disp));
+ g_signal_connect (G_OBJECT (disp), "draw",
+ G_CALLBACK (load_graph_draw), graph);
+ g_signal_connect (G_OBJECT(disp), "configure_event",
+ G_CALLBACK (load_graph_configure), graph);
+ g_signal_connect (G_OBJECT(disp), "destroy",
+ G_CALLBACK (load_graph_destroy), graph);
+ g_signal_connect (G_OBJECT(disp), "state-flags-changed",
+ G_CALLBACK (load_graph_state_changed), graph);
+ g_signal_connect (G_OBJECT(disp), "style-updated",
+ G_CALLBACK (load_graph_style_updated), graph);
+
+ gtk_widget_set_events (GTK_WIDGET (disp), GDK_EXPOSURE_MASK);
+
+ gtk_box_pack_start (main_widget, GTK_WIDGET (disp), TRUE, TRUE, 0);
+
+ data = std::vector<double*>(num_points);
+ /* Allocate data in a contiguous block */
+ data_block = std::vector<double>(n * num_points, -1.0);
+
+ for (guint i = 0; i < num_points; ++i)
+ data[i] = &data_block[0] + i * n;
+
+ gtk_widget_show_all (GTK_WIDGET (main_widget));
+}
+
+void
+load_graph_start (LoadGraph *graph)
+{
+ if (!graph->timer_index) {
+ // Update the data two times so the graph
+ // doesn't wait one cycle to start drawing.
+ load_graph_update_data(graph);
+ load_graph_update(graph);
+
+ graph->timer_index = g_timeout_add (graph->speed,
+ load_graph_update,
+ graph);
+ }
+
+ graph->draw = TRUE;
+}
+
+void
+load_graph_stop (LoadGraph *graph)
+{
+ /* don't draw anymore, but continue to poll */
+ graph->draw = FALSE;
+}
+
+void
+load_graph_change_speed (LoadGraph *graph,
+ guint new_speed)
+{
+ if (graph->speed == new_speed)
+ return;
+
+ graph->speed = new_speed;
+
+ if (graph->timer_index) {
+ g_source_remove (graph->timer_index);
+ graph->timer_index = g_timeout_add (graph->speed,
+ load_graph_update,
+ graph);
+ }
+
+ graph->clear_background();
+}
+
+void
+load_graph_change_num_points(LoadGraph *graph,
+ guint new_num_points)
+{
+ // Don't do anything if the value didn't change.
+ if (graph->num_points == new_num_points)
+ return;
+
+ // Sort the values in the data_block vector in the order they were accessed in by the pointers in data.
+ std::rotate(graph->data_block.begin(),
+ graph->data_block.begin() + (graph->num_points - graph->latest) * graph->n,
+ graph->data_block.end());
+
+ // Reset rotation counter.
+ graph->latest = 0;
+
+ // Resize the vectors to the new amount of data points.
+ // Fill the new values with -1.
+ graph->data.resize(new_num_points);
+ graph->data_block.resize(graph->n * new_num_points, -1.0);
+ if (graph->type == LOAD_GRAPH_NET) {
+ graph->net.values.resize(new_num_points);
+ }
+
+ // Replace the pointers in data, to match the new data_block values.
+ for (guint i = 0; i < new_num_points; ++i) {
+ graph->data[i] = &graph->data_block[0] + i * graph->n;
+ }
+
+ // Set the actual number of data points to be used by the graph.
+ graph->num_points = new_num_points;
+
+ // Force the scale to be redrawn.
+ graph->clear_background();
+}
+
+
+LoadGraphLabels*
+load_graph_get_labels (LoadGraph *graph)
+{
+ return &graph->labels;
+}
+
+GtkBox*
+load_graph_get_widget (LoadGraph *graph)
+{
+ return graph->main_widget;
+}
+
+GsmColorButton*
+load_graph_get_mem_color_picker(LoadGraph *graph)
+{
+ return graph->mem_color_picker;
+}
+
+GsmColorButton*
+load_graph_get_swap_color_picker(LoadGraph *graph)
+{
+ return graph->swap_color_picker;
+}
diff --git a/src/load-graph.h b/src/load-graph.h
new file mode 100644
index 0000000..1a519ad
--- /dev/null
+++ b/src/load-graph.h
@@ -0,0 +1,146 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_LOAD_GRAPH_H_
+#define _GSM_LOAD_GRAPH_H_
+
+#include <glib.h>
+#include <glibtop/cpu.h>
+
+#include "legacy/gsm_color_button.h"
+#include "util.h"
+#include "settings-keys.h"
+
+enum
+{
+ LOAD_GRAPH_CPU,
+ LOAD_GRAPH_MEM,
+ LOAD_GRAPH_NET
+};
+
+enum
+{
+ CPU_TOTAL,
+ CPU_USED,
+ N_CPU_STATES
+};
+
+struct LoadGraphLabels
+{
+ GtkLabel *cpu[GLIBTOP_NCPU];
+ GtkLabel *memory;
+ GtkLabel *swap;
+ GtkLabel *net_in;
+ GtkLabel *net_in_total;
+ GtkLabel *net_out;
+ GtkLabel *net_out_total;
+};
+
+struct LoadGraph
+ : private procman::NonCopyable
+{
+ static const unsigned GRAPH_MIN_HEIGHT = 40;
+
+ LoadGraph(guint type);
+ ~LoadGraph();
+
+ unsigned num_bars() const;
+ void clear_background();
+ bool is_logarithmic_scale() const;
+ char* get_caption(guint index);
+ float translate_to_log_partial_if_needed(float position_partial);
+
+ double fontsize;
+ double rmargin;
+ /* left margin */
+ double indent;
+
+ guint n;
+ gint type;
+ guint speed;
+ guint num_points;
+ guint latest;
+ guint draw_width, draw_height;
+ guint render_counter;
+ guint frames_per_unit;
+ guint graph_dely;
+ guint real_draw_height;
+ double graph_delx;
+ guint graph_buffer_offset;
+
+ std::vector<GdkRGBA> colors;
+
+ std::vector<double> data_block;
+ std::vector<double*> data;
+
+ GtkBox *main_widget;
+ GtkDrawingArea *disp;
+
+ cairo_surface_t *background;
+
+ guint timer_index;
+
+ gboolean draw;
+
+ LoadGraphLabels labels;
+ GsmColorButton *mem_color_picker;
+ GsmColorButton *swap_color_picker;
+
+ Glib::RefPtr<Gio::Settings> font_settings;
+
+ /* union { */
+ struct CPU
+ {
+ guint now; /* 0 -> current, 1 -> last
+ now ^ 1 each time */
+ /* times[now], times[now ^ 1] is last */
+ guint64 times[2][GLIBTOP_NCPU][N_CPU_STATES];
+ } cpu;
+
+ struct NET
+ {
+ guint64 last_in, last_out;
+ guint64 time;
+ guint64 max;
+ std::vector<unsigned> values;
+ } net;
+ /* }; */
+};
+
+/* Force a drawing update */
+void
+load_graph_queue_draw (LoadGraph *g);
+
+/* Start load graph. */
+void
+load_graph_start (LoadGraph *g);
+
+/* Stop load graph. */
+void
+load_graph_stop (LoadGraph *g);
+
+/* Change load graph speed and restart it if it has been previously started */
+void
+load_graph_change_speed (LoadGraph *g,
+ guint new_speed);
+
+/* Change load graph data points and restart it if it has been previously started */
+void
+load_graph_change_num_points(LoadGraph *g,
+ guint new_num_points);
+
+/* Clear the history data. */
+void
+load_graph_reset (LoadGraph *g);
+
+LoadGraphLabels*
+load_graph_get_labels (LoadGraph *g) G_GNUC_CONST;
+
+GtkBox*
+load_graph_get_widget (LoadGraph *g) G_GNUC_CONST;
+
+GsmColorButton*
+load_graph_get_mem_color_picker(LoadGraph *g) G_GNUC_CONST;
+
+GsmColorButton*
+load_graph_get_swap_color_picker(LoadGraph *g) G_GNUC_CONST;
+
+#endif /* _GSM_LOAD_GRAPH_H_ */
diff --git a/src/lsof.cpp b/src/lsof.cpp
new file mode 100644
index 0000000..15c3a59
--- /dev/null
+++ b/src/lsof.cpp
@@ -0,0 +1,316 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <gtkmm/messagedialog.h>
+#include <glib/gi18n.h>
+#include <glibtop/procopenfiles.h>
+
+#include <sys/wait.h>
+
+#include <set>
+#include <string>
+#include <sstream>
+#include <iterator>
+
+#include <glibmm/regex.h>
+
+#include "application.h"
+#include "lsof.h"
+#include "util.h"
+
+
+using std::string;
+
+
+namespace
+{
+
+ class Lsof
+ {
+ Glib::RefPtr<Glib::Regex> re;
+
+ bool matches(const string &filename) const
+ {
+ return this->re->match(filename);
+ }
+
+ public:
+
+ Lsof(const string &pattern, bool caseless)
+ {
+ Glib::RegexCompileFlags flags = static_cast<Glib::RegexCompileFlags>(0);
+
+ if (caseless)
+ flags |= Glib::REGEX_CASELESS;
+
+ this->re = Glib::Regex::create(pattern, flags);
+ }
+
+
+ template<typename OutputIterator>
+ void search(const ProcInfo &info, OutputIterator out) const
+ {
+ glibtop_open_files_entry *entries;
+ glibtop_proc_open_files buf;
+
+ entries = glibtop_get_proc_open_files(&buf, info.pid);
+
+ for (unsigned i = 0; i != buf.number; ++i) {
+ if (entries[i].type & GLIBTOP_FILE_TYPE_FILE) {
+ const string filename(entries[i].info.file.name);
+ if (this->matches(filename))
+ *out++ = filename;
+ }
+ }
+
+ g_free(entries);
+ }
+ };
+
+
+
+ // GUI Stuff
+
+
+ enum ProcmanLsof {
+ PROCMAN_LSOF_COL_PIXBUF,
+ PROCMAN_LSOF_COL_PROCESS,
+ PROCMAN_LSOF_COL_PID,
+ PROCMAN_LSOF_COL_FILENAME,
+ PROCMAN_LSOF_NCOLS
+ };
+
+
+ struct GUI : private procman::NonCopyable {
+
+ GtkListStore *model;
+ GtkSearchEntry *entry;
+ GtkWindow *window;
+ GtkLabel *count;
+ GsmApplication *app;
+ bool case_insensitive;
+ bool regex_error_displayed;
+
+
+ GUI()
+ : model(NULL),
+ entry(NULL),
+ window(NULL),
+ count(NULL),
+ app(NULL),
+ case_insensitive(),
+ regex_error_displayed(false)
+ {
+ procman_debug("New Lsof GUI %p", this);
+ }
+
+
+ ~GUI()
+ {
+ procman_debug("Destroying Lsof GUI %p", this);
+ }
+
+
+ void update_count(unsigned count)
+ {
+ gchar *title;
+ if (this->pattern().length() == 0) {
+ title = g_strdup_printf (ngettext("%d open file", "%d open files", count), count);
+ } else {
+ title = g_strdup_printf (ngettext("%d matching open file", "%d matching open files", count), count);
+ }
+ gtk_window_set_title(this->window, title);
+ g_free (title);
+ }
+
+
+ string pattern() const
+ {
+ return gtk_entry_get_text(GTK_ENTRY (this->entry));
+ }
+
+
+ void search()
+ {
+ typedef std::set<string> MatchSet;
+
+ bool regex_error = false;
+
+ gtk_list_store_clear(this->model);
+ try {
+ Lsof lsof(this->pattern(), this->case_insensitive);
+
+ unsigned count = 0;
+
+ for (const auto& v : app->processes) {
+ const auto& info = v.second;
+ MatchSet matches;
+ lsof.search(info, std::inserter(matches, matches.begin()));
+ count += matches.size();
+
+ for (const auto& match : matches) {
+ GtkTreeIter file;
+ gtk_list_store_append(this->model, &file);
+ gtk_list_store_set(this->model, &file,
+ PROCMAN_LSOF_COL_PIXBUF, info.pixbuf->gobj(),
+ PROCMAN_LSOF_COL_PROCESS, info.name.c_str(),
+ PROCMAN_LSOF_COL_PID, info.pid,
+ PROCMAN_LSOF_COL_FILENAME, match.c_str(),
+ -1);
+ }
+ }
+
+ this->update_count(count);
+ }
+ catch (Glib::RegexError& error) {
+ regex_error = true;
+ }
+
+ if (regex_error && !this->regex_error_displayed) {
+ this->regex_error_displayed = true;
+ gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(entry)), GTK_STYLE_CLASS_ERROR);
+ }
+ else if (!regex_error && this->regex_error_displayed) {
+ this->regex_error_displayed = false;
+ gtk_style_context_remove_class(gtk_widget_get_style_context(GTK_WIDGET(entry)), GTK_STYLE_CLASS_ERROR);
+ }
+ }
+
+
+ static void search_changed(GtkSearchEntry *, gpointer data)
+ {
+ static_cast<GUI*>(data)->search();
+ }
+
+
+ static void close_button_clicked(GtkButton *, gpointer data)
+ {
+ GUI *gui = static_cast<GUI*>(data);
+ gtk_widget_destroy(GTK_WIDGET(gui->window));
+ delete gui;
+ }
+
+
+ static void case_button_toggled(GtkToggleButton *button, gpointer data)
+ {
+ bool state = gtk_toggle_button_get_active(button);
+ static_cast<GUI*>(data)->case_insensitive = state;
+ }
+
+
+ static gboolean window_delete_event(GtkWidget *, GdkEvent *, gpointer data)
+ {
+ delete static_cast<GUI*>(data);
+ return FALSE;
+ }
+
+ };
+}
+
+
+
+
+void procman_lsof(GsmApplication *app)
+{
+ GtkListStore *model = \
+ gtk_list_store_new(PROCMAN_LSOF_NCOLS,
+ GDK_TYPE_PIXBUF, // PROCMAN_LSOF_COL_PIXBUF
+ G_TYPE_STRING, // PROCMAN_LSOF_COL_PROCESS
+ G_TYPE_UINT, // PROCMAN_LSOF_COL_PID
+ G_TYPE_STRING // PROCMAN_LSOF_COL_FILENAME
+ );
+
+ GtkTreeView *tree = GTK_TREE_VIEW (gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)));
+ g_object_unref(model);
+
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+
+ // PIXBUF / PROCESS
+
+ column = gtk_tree_view_column_new();
+
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes(column, renderer,
+ "pixbuf", PROCMAN_LSOF_COL_PIXBUF,
+ NULL);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes(column, renderer,
+ "text", PROCMAN_LSOF_COL_PROCESS,
+ NULL);
+
+ gtk_tree_view_column_set_title(column, _("Process"));
+ gtk_tree_view_column_set_sort_column_id(column, PROCMAN_LSOF_COL_PROCESS);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_column_set_min_width(column, 10);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), PROCMAN_LSOF_COL_PROCESS,
+ GTK_SORT_ASCENDING);
+
+
+ // PID
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("PID"), renderer,
+ "text", PROCMAN_LSOF_COL_PID,
+ NULL);
+ gtk_tree_view_column_set_sort_column_id(column, PROCMAN_LSOF_COL_PID);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+
+ // FILENAME
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(_("Filename"), renderer,
+ "text", PROCMAN_LSOF_COL_FILENAME,
+ NULL);
+ gtk_tree_view_column_set_sort_column_id(column, PROCMAN_LSOF_COL_FILENAME);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+
+ GtkWindow *dialog;
+
+ GtkBuilder *builder = gtk_builder_new ();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/lsof.ui", NULL);
+
+ dialog = GTK_WINDOW (gtk_builder_get_object (builder, "lsof_dialog"));
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (app->main_window));
+
+ GtkSearchEntry *entry = GTK_SEARCH_ENTRY (gtk_builder_get_object (builder, "entry"));
+
+ GtkCheckButton *case_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "case_button"));
+
+ // Scrolled TreeView
+ GtkScrolledWindow *scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "scrolled"));
+
+ gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET (tree));
+
+ GUI *gui = new GUI; // wil be deleted by the close button or delete-event
+ gui->app = app;
+ gui->model = model;
+ gui->window = dialog;
+ gui->entry = entry;
+ gui->case_insensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (case_button));
+
+ g_signal_connect(G_OBJECT(entry), "search-changed",
+ G_CALLBACK(GUI::search_changed), gui);
+ g_signal_connect(G_OBJECT(case_button), "toggled",
+ G_CALLBACK(GUI::case_button_toggled), gui);
+ g_signal_connect(G_OBJECT(dialog), "delete-event",
+ G_CALLBACK(GUI::window_delete_event), gui);
+
+ gtk_builder_connect_signals (builder, NULL);
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (GsmApplication::get()->main_window));
+ gtk_widget_show_all(GTK_WIDGET (dialog));
+ gui->search ();
+
+ g_object_unref (G_OBJECT (builder));
+}
+
diff --git a/src/lsof.h b/src/lsof.h
new file mode 100644
index 0000000..8b8111a
--- /dev/null
+++ b/src/lsof.h
@@ -0,0 +1,10 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_LSOF_H_
+#define _GSM_LSOF_H_
+
+#include <glib.h>
+#include "application.h"
+
+void procman_lsof(GsmApplication *app);
+
+#endif /* _GSM_LSOF_H_ */
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..8bd33e8
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,39 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+
+#include <locale.h>
+#include <glib/gi18n.h>
+
+#include "application.h"
+
+
+int
+main (int argc, char *argv[])
+{
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+ setlocale (LC_ALL, "");
+
+ Glib::RefPtr<GsmApplication> application = GsmApplication::get();
+ return application->run (argc, argv);
+}
+
diff --git a/src/memmaps.cpp b/src/memmaps.cpp
new file mode 100644
index 0000000..8827d9f
--- /dev/null
+++ b/src/memmaps.cpp
@@ -0,0 +1,491 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glibtop/procmap.h>
+#include <glibtop/mountlist.h>
+#include <sys/stat.h>
+#include <glib/gi18n.h>
+
+#include <string>
+#include <map>
+#include <sstream>
+#include <iomanip>
+#include <stdexcept>
+
+using std::string;
+
+
+#include "application.h"
+#include "memmaps.h"
+#include "proctable.h"
+#include "settings-keys.h"
+#include "legacy/treeview.h"
+#include "util.h"
+
+
+/* be careful with this enum, you could break the column names */
+enum
+{
+ MMAP_COL_FILENAME,
+ MMAP_COL_VMSTART,
+ MMAP_COL_VMEND,
+ MMAP_COL_VMSZ,
+ MMAP_COL_FLAGS,
+ MMAP_COL_VMOFFSET,
+ MMAP_COL_PRIVATE_CLEAN,
+ MMAP_COL_PRIVATE_DIRTY,
+ MMAP_COL_SHARED_CLEAN,
+ MMAP_COL_SHARED_DIRTY,
+ MMAP_COL_DEVICE,
+ MMAP_COL_INODE,
+ MMAP_COL_MAX
+};
+
+
+namespace
+{
+ class OffsetFormater
+ {
+ string format;
+
+ public:
+
+ void set(const glibtop_map_entry &last_map)
+ {
+ this->format = (last_map.end <= G_MAXUINT32) ? "%08" G_GINT64_MODIFIER "x" : "%016" G_GINT64_MODIFIER "x";
+ }
+
+ string operator()(guint64 v) const
+ {
+ char buffer[17];
+ g_snprintf(buffer, sizeof buffer, this->format.c_str(), v);
+ return buffer;
+ }
+ };
+
+ class InodeDevices
+ {
+ typedef std::map<guint16, string> Map;
+ Map devices;
+
+ public:
+
+ void update()
+ {
+ this->devices.clear();
+
+ glibtop_mountlist list;
+ glibtop_mountentry *entries = glibtop_get_mountlist(&list, 1);
+
+ for (unsigned i = 0; i != list.number; ++i) {
+ struct stat buf;
+
+ if (stat(entries[i].devname, &buf) != -1)
+ this->devices[buf.st_rdev] = entries[i].devname;
+ }
+
+ g_free(entries);
+ }
+
+ string get(guint64 dev64)
+ {
+ if (dev64 == 0)
+ return "";
+
+ guint16 dev = dev64 & 0xffff;
+
+ if (dev != dev64)
+ g_warning("weird device %" G_GINT64_MODIFIER "x", dev64);
+
+ Map::iterator it(this->devices.find(dev));
+
+ if (it != this->devices.end())
+ return it->second;
+
+ guint8 major, minor;
+ major = dev >> 8;
+ minor = dev;
+
+ std::ostringstream out;
+ out << std::hex
+ << std::setfill('0')
+ << std::setw(2) << unsigned(major)
+ << ':'
+ << std::setw(2) << unsigned(minor);
+
+ this->devices[dev] = out.str();
+ return out.str();
+ }
+ };
+
+
+ class MemMapsData
+ {
+ public:
+ guint timer;
+ GsmTreeView *tree;
+ ProcInfo *info;
+ OffsetFormater format;
+ mutable InodeDevices devices;
+
+ MemMapsData(GsmTreeView *a_tree)
+ : timer(),
+ tree(a_tree),
+ info(NULL),
+ format(),
+ devices()
+ {
+ gsm_tree_view_load_state (this->tree);
+ }
+
+ ~MemMapsData()
+ {
+ gsm_tree_view_save_state (this->tree);
+ }
+ };
+}
+
+
+struct glibtop_map_entry_cmp
+{
+ bool operator()(const glibtop_map_entry &a, const guint64 start) const
+ {
+ return a.start < start;
+ }
+
+ bool operator()(const guint64 &start, const glibtop_map_entry &a) const
+ {
+ return start < a.start;
+ }
+
+};
+
+
+static void
+update_row(GtkTreeModel *model, GtkTreeIter &row, const MemMapsData &mm, const glibtop_map_entry *memmaps)
+{
+ guint64 size;
+ string filename, device;
+ string vmstart, vmend, vmoffset;
+ char flags[5] = "----";
+
+ size = memmaps->end - memmaps->start;
+
+ if(memmaps->perm & GLIBTOP_MAP_PERM_READ) flags [0] = 'r';
+ if(memmaps->perm & GLIBTOP_MAP_PERM_WRITE) flags [1] = 'w';
+ if(memmaps->perm & GLIBTOP_MAP_PERM_EXECUTE) flags [2] = 'x';
+ if(memmaps->perm & GLIBTOP_MAP_PERM_SHARED) flags [3] = 's';
+ if(memmaps->perm & GLIBTOP_MAP_PERM_PRIVATE) flags [3] = 'p';
+
+ if (memmaps->flags & (1 << GLIBTOP_MAP_ENTRY_FILENAME))
+ filename = memmaps->filename;
+
+ vmstart = mm.format(memmaps->start);
+ vmend = mm.format(memmaps->end);
+ vmoffset = mm.format(memmaps->offset);
+ device = mm.devices.get(memmaps->device);
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &row,
+ MMAP_COL_FILENAME, filename.c_str(),
+ MMAP_COL_VMSTART, vmstart.c_str(),
+ MMAP_COL_VMEND, vmend.c_str(),
+ MMAP_COL_VMSZ, size,
+ MMAP_COL_FLAGS, flags,
+ MMAP_COL_VMOFFSET, vmoffset.c_str(),
+ MMAP_COL_PRIVATE_CLEAN, memmaps->private_clean,
+ MMAP_COL_PRIVATE_DIRTY, memmaps->private_dirty,
+ MMAP_COL_SHARED_CLEAN, memmaps->shared_clean,
+ MMAP_COL_SHARED_DIRTY, memmaps->shared_dirty,
+ MMAP_COL_DEVICE, device.c_str(),
+ MMAP_COL_INODE, memmaps->inode,
+ -1);
+}
+
+
+
+
+static void
+update_memmaps_dialog (MemMapsData *mmdata)
+{
+ GtkTreeModel *model;
+ glibtop_map_entry *memmaps;
+ glibtop_proc_map procmap;
+
+ memmaps = glibtop_get_proc_map (&procmap, mmdata->info->pid);
+ /* process has disappeared */
+ if(!memmaps or procmap.number == 0) return;
+
+ mmdata->format.set(memmaps[procmap.number - 1]);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (mmdata->tree));
+
+ GtkTreeIter iter;
+
+ typedef std::map<guint64, GtkTreeIter> IterCache;
+ IterCache iter_cache;
+
+ /*
+ removes the old maps and
+ also fills a cache of start -> iter in order to speed
+ up add
+ */
+
+ if (gtk_tree_model_get_iter_first(model, &iter)) {
+ while (true) {
+ char *vmstart = 0;
+ guint64 start;
+ gtk_tree_model_get(model, &iter,
+ MMAP_COL_VMSTART, &vmstart,
+ -1);
+
+ try {
+ std::istringstream(vmstart) >> std::hex >> start;
+ } catch (std::logic_error &e) {
+ g_warning("Could not parse %s", vmstart);
+ start = 0;
+ }
+
+ g_free(vmstart);
+
+ bool found = std::binary_search(memmaps, memmaps + procmap.number,
+ start, glibtop_map_entry_cmp());
+
+ if (found) {
+ iter_cache[start] = iter;
+ if (!gtk_tree_model_iter_next(model, &iter))
+ break;
+ } else {
+ if (!gtk_list_store_remove(GTK_LIST_STORE(model), &iter))
+ break;
+ }
+ }
+ }
+
+ mmdata->devices.update();
+
+ /*
+ add the new maps
+ */
+
+ for (guint i = 0; i != procmap.number; i++) {
+ GtkTreeIter iter;
+ IterCache::iterator it(iter_cache.find(memmaps[i].start));
+
+ if (it != iter_cache.end())
+ iter = it->second;
+ else
+ gtk_list_store_prepend(GTK_LIST_STORE(model), &iter);
+
+ update_row(model, iter, *mmdata, &memmaps[i]);
+ }
+
+ g_free (memmaps);
+}
+
+
+
+static void
+dialog_response (GtkDialog * dialog, gint response_id, gpointer data)
+{
+ MemMapsData * const mmdata = static_cast<MemMapsData*>(data);
+
+ g_source_remove (mmdata->timer);
+
+ delete mmdata;
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+
+static MemMapsData*
+create_memmapsdata (GsmApplication *app)
+{
+ GsmTreeView *tree;
+ GtkListStore *model;
+ guint i;
+
+ const gchar * const titles[] = {
+ N_("Filename"),
+ // xgettext: virtual memory start
+ N_("VM Start"),
+ // xgettext: virtual memory end
+ N_("VM End"),
+ // xgettext: virtual memory syze
+ N_("VM Size"),
+ N_("Flags"),
+ // xgettext: virtual memory offset
+ N_("VM Offset"),
+ // xgettext: memory that has not been modified since
+ // it has been allocated
+ N_("Private clean"),
+ // xgettext: memory that has been modified since it
+ // has been allocated
+ N_("Private dirty"),
+ // xgettext: shared memory that has not been modified
+ // since it has been allocated
+ N_("Shared clean"),
+ // xgettext: shared memory that has been modified
+ // since it has been allocated
+ N_("Shared dirty"),
+ N_("Device"),
+ N_("Inode")
+ };
+
+ model = gtk_list_store_new (MMAP_COL_MAX,
+ G_TYPE_STRING, /* MMAP_COL_FILENAME */
+ G_TYPE_STRING, /* MMAP_COL_VMSTART */
+ G_TYPE_STRING, /* MMAP_COL_VMEND */
+ G_TYPE_UINT64, /* MMAP_COL_VMSZ */
+ G_TYPE_STRING, /* MMAP_COL_FLAGS */
+ G_TYPE_STRING, /* MMAP_COL_VMOFFSET */
+ G_TYPE_UINT64, /* MMAP_COL_PRIVATE_CLEAN */
+ G_TYPE_UINT64, /* MMAP_COL_PRIVATE_DIRTY */
+ G_TYPE_UINT64, /* MMAP_COL_SHARED_CLEAN */
+ G_TYPE_UINT64, /* MMAP_COL_SHARED_DIRTY */
+ G_TYPE_STRING, /* MMAP_COL_DEVICE */
+ G_TYPE_UINT64 /* MMAP_COL_INODE */
+ );
+
+ auto settings = g_settings_get_child (app->settings->gobj (), GSM_SETTINGS_CHILD_MEMMAP);
+
+ tree = gsm_tree_view_new (settings, FALSE);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tree), GTK_TREE_MODEL (model));
+ g_object_unref (G_OBJECT (model));
+
+ auto font = get_monospace_system_font_name ();
+
+ for (i = 0; i < MMAP_COL_MAX; i++) {
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *col;
+ PangoAttrList *attrs;
+
+ cell = gtk_cell_renderer_text_new();
+ col = gtk_tree_view_column_new();
+ gtk_tree_view_column_pack_start(col, cell, TRUE);
+ gtk_tree_view_column_set_title(col, _(titles[i]));
+ gtk_tree_view_column_set_resizable(col, TRUE);
+ gtk_tree_view_column_set_sort_column_id(col, i);
+ gtk_tree_view_column_set_reorderable(col, TRUE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), col);
+
+ attrs = make_tnum_attr_list ();
+ g_object_set (cell, "attributes", attrs, NULL);
+ g_clear_pointer (&attrs, pango_attr_list_unref);
+
+ switch (i) {
+ case MMAP_COL_PRIVATE_CLEAN:
+ case MMAP_COL_PRIVATE_DIRTY:
+ case MMAP_COL_SHARED_CLEAN:
+ case MMAP_COL_SHARED_DIRTY:
+ case MMAP_COL_VMSZ:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::size_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+
+ g_object_set(cell, "xalign", 1.0f, NULL);
+ break;
+
+ default:
+ gtk_tree_view_column_set_attributes(col, cell, "text", i, NULL);
+ break;
+ }
+
+ switch (i) {
+ case MMAP_COL_VMSTART:
+ case MMAP_COL_VMEND:
+ case MMAP_COL_FLAGS:
+ case MMAP_COL_VMOFFSET:
+ g_object_set (cell, "font", font.c_str (), NULL);
+ break;
+ }
+ }
+
+ return new MemMapsData(tree);
+}
+
+
+static gboolean
+memmaps_timer (gpointer data)
+{
+ MemMapsData * const mmdata = static_cast<MemMapsData*>(data);
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (mmdata->tree));
+ g_assert(model);
+
+ update_memmaps_dialog (mmdata);
+
+ return TRUE;
+}
+
+
+static void
+create_single_memmaps_dialog (GtkTreeModel *model, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ GsmApplication *app = static_cast<GsmApplication *>(data);
+ MemMapsData *mmdata;
+ GtkDialog *memmapsdialog;
+ GtkBox *dialog_box;
+ GtkLabel *label;
+ GtkScrolledWindow *scrolled;
+ ProcInfo *info;
+
+ gtk_tree_model_get (model, iter, COL_POINTER, &info, -1);
+
+ if (!info)
+ return;
+
+ mmdata = create_memmapsdata (app);
+ mmdata->info = info;
+
+ memmapsdialog = GTK_DIALOG (g_object_new (GTK_TYPE_DIALOG,
+ "title", _("Memory Maps"),
+ "use-header-bar", TRUE,
+ "destroy-with-parent", TRUE, NULL));
+
+ gtk_window_set_resizable(GTK_WINDOW(memmapsdialog), TRUE);
+ gtk_window_set_default_size(GTK_WINDOW(memmapsdialog), 620, 400);
+ gtk_container_set_border_width(GTK_CONTAINER(memmapsdialog), 5);
+
+ dialog_box = GTK_BOX (gtk_dialog_get_content_area (memmapsdialog));
+ gtk_container_set_border_width (GTK_CONTAINER (dialog_box), 5);
+
+ label = procman_make_label_for_mmaps_or_ofiles (
+ _("_Memory maps for process “%s” (PID %u):"),
+ info->name.c_str(),
+ info->pid);
+
+ gtk_box_pack_start (dialog_box, GTK_WIDGET (label), FALSE, TRUE, 0);
+
+
+ scrolled = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_IN);
+
+ gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (mmdata->tree));
+ gtk_label_set_mnemonic_widget (label, GTK_WIDGET (mmdata->tree));
+
+ gtk_box_pack_start (dialog_box, GTK_WIDGET (scrolled), TRUE, TRUE, 0);
+
+ g_signal_connect(G_OBJECT(memmapsdialog), "response",
+ G_CALLBACK(dialog_response), mmdata);
+
+ gtk_window_set_transient_for (GTK_WINDOW (memmapsdialog), GTK_WINDOW (GsmApplication::get()->main_window));
+ gtk_widget_show_all (GTK_WIDGET (memmapsdialog));
+ mmdata->timer = g_timeout_add_seconds (5, memmaps_timer, mmdata);
+
+ update_memmaps_dialog (mmdata);
+}
+
+
+void
+create_memmaps_dialog (GsmApplication *app)
+{
+ /* TODO: do we really want to open multiple dialogs ? */
+ gtk_tree_selection_selected_foreach (app->selection, create_single_memmaps_dialog,
+ app);
+}
diff --git a/src/memmaps.h b/src/memmaps.h
new file mode 100644
index 0000000..6a9bd72
--- /dev/null
+++ b/src/memmaps.h
@@ -0,0 +1,10 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_MEMMAPS_H_
+#define _GSM_MEMMAPS_H_
+
+#include <glib.h>
+#include "application.h"
+
+void create_memmaps_dialog (GsmApplication *app);
+
+#endif /* _GSM_MEMMAPS_H_ */
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..6b53736
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,110 @@
+subdir('legacy')
+
+system_monitor_sources = [
+ 'application.cpp',
+ 'argv.cpp',
+ 'cgroups.cpp',
+ 'disks.cpp',
+ 'gsm_gksu.cpp',
+ 'gsm_gnomesu.cpp',
+ 'gsm_pkexec.cpp',
+ 'interface.cpp',
+ 'load-graph.cpp',
+ 'lsof.cpp',
+ 'main.cpp',
+ 'setaffinity.cpp',
+ 'memmaps.cpp',
+ 'openfiles.cpp',
+ 'prefsdialog.cpp',
+ 'prettytable.cpp',
+ 'procactions.cpp',
+ 'procdialogs.cpp',
+ 'procproperties.cpp',
+ 'proctable.cpp',
+ 'selinux.cpp',
+ 'smooth_refresh.cpp',
+ 'systemd.cpp',
+ 'util.cpp',
+]
+
+system_monitor_headers = [
+ 'prettytable.h',
+ 'procdialogs.h',
+ 'cgroups.h',
+ 'application.h',
+ 'smooth_refresh.h',
+ 'gsm_gnomesu.h',
+ 'openfiles.h',
+ 'procproperties.h',
+ 'lsof.h',
+ 'proctable.h',
+ 'settings-keys.h',
+ 'setaffinity.h',
+ 'memmaps.h',
+ 'procactions.h',
+ 'systemd.h',
+ 'argv.h',
+ 'prefsdialog.h',
+ 'selinux.h',
+ 'util.h',
+ 'gsm_gksu.h',
+ 'interface.h',
+ 'load-graph.h',
+ 'disks.h',
+ 'gsm_pkexec.h',
+ 'defaulttable.h',
+ 'legacy/treeview.h',
+ 'legacy/e_date.h',
+ 'legacy/gsm_color_button.h',
+]
+
+gsm_resource_dir = join_paths(get_option('datadir'), meson.project_name())
+gsm_resource = gnome.compile_resources(
+ 'gsm',
+ 'gsm.gresource.xml',
+ gresource_bundle: true,
+ source_dir: meson.source_root(),
+ install: true,
+ install_dir: gsm_resource_dir,
+)
+
+gsm_schemas = configure_file(
+ input : 'org.gnome.gnome-system-monitor.gschema.xml.in',
+ output: 'org.gnome.gnome-system-monitor.gschema.xml',
+ configuration: dataconf,
+ install: true,
+ install_dir: join_paths(get_option('datadir'), 'glib-2.0', 'schemas'),
+)
+
+gsm_gsettings = gnome.mkenums('org.gnome.gnome-system-monitor.enums.xml',
+ sources: system_monitor_headers,
+ comments: '<!-- @comment@ -->',
+ fhead: '<schemalist>',
+ vhead: ' <@type@ id=\'org.gnome.gnome-system-monitor.@EnumName@\'>',
+ vprod: ' <value nick=\'@valuenick@\' value=\'@valuenum@\'/>',
+ vtail: ' </@type@>',
+ ftail: '</schemalist>',
+ install_header: true,
+ install_dir: join_paths(get_option('datadir'), 'glib-2.0', 'schemas'),
+)
+
+
+executable(meson.project_name(),
+ system_monitor_sources,
+ include_directories: rootInclude,
+ dependencies: [
+ gmodule,
+ gtkmm,
+ atkmm,
+ libgtop,
+ libhandy,
+ libsystemd,
+ libwnck,
+ ],
+ link_with: libgsm_legacy,
+ cpp_args: [
+ '-DGSM_RESOURCE_FILE="@0@"'.format(join_paths(
+ get_option('prefix'), gsm_resource_dir, 'gsm.gresource')),
+ ],
+ install: true,
+)
diff --git a/src/openfiles.cpp b/src/openfiles.cpp
new file mode 100644
index 0000000..edf359b
--- /dev/null
+++ b/src/openfiles.cpp
@@ -0,0 +1,371 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <glibtop/procopenfiles.h>
+#include <sys/stat.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "application.h"
+#include "openfiles.h"
+#include "proctable.h"
+#include "util.h"
+#include "settings-keys.h"
+#include "legacy/treeview.h"
+
+#ifndef NI_IDN
+const int NI_IDN = 0;
+#endif
+
+enum
+{
+ COL_FD,
+ COL_TYPE,
+ COL_OBJECT,
+ COL_OPENFILE_STRUCT,
+ NUM_OPENFILES_COL
+};
+
+
+static const char*
+get_type_name(enum glibtop_file_type t)
+{
+ switch(t)
+ {
+ case GLIBTOP_FILE_TYPE_FILE:
+ return _("file");
+ case GLIBTOP_FILE_TYPE_PIPE:
+ return _("pipe");
+ case GLIBTOP_FILE_TYPE_INET6SOCKET:
+ return _("IPv6 network connection");
+ case GLIBTOP_FILE_TYPE_INETSOCKET:
+ return _("IPv4 network connection");
+ case GLIBTOP_FILE_TYPE_LOCALSOCKET:
+ return _("local socket");
+ default:
+ return _("unknown type");
+ }
+}
+
+
+
+static char *
+friendlier_hostname(const char *addr_str, int port)
+{
+ struct addrinfo hints = { };
+ struct addrinfo *res = NULL;
+ char hostname[NI_MAXHOST];
+ char service[NI_MAXSERV];
+ char port_str[6];
+
+ if (!addr_str[0]) return g_strdup("");
+
+ snprintf(port_str, sizeof port_str, "%d", port);
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if (getaddrinfo(addr_str, port_str, &hints, &res))
+ goto failsafe;
+
+ if (getnameinfo(res->ai_addr, res->ai_addrlen, hostname,
+ sizeof hostname, service, sizeof service, NI_IDN))
+ goto failsafe;
+
+ if (res) freeaddrinfo(res);
+ return g_strdup_printf("%s, TCP port %d (%s)", hostname, port, service);
+
+ failsafe:
+ if (res) freeaddrinfo(res);
+ return g_strdup_printf("%s, TCP port %d", addr_str, port);
+}
+
+
+
+static void
+add_new_files (gpointer key, gpointer value, gpointer data)
+{
+ glibtop_open_files_entry *openfiles = static_cast<glibtop_open_files_entry*>(value);
+
+ GtkTreeModel *model = static_cast<GtkTreeModel*>(data);
+ GtkTreeIter row;
+
+ char *object;
+
+ switch(openfiles->type)
+ {
+ case GLIBTOP_FILE_TYPE_FILE:
+ object = g_strdup(openfiles->info.file.name);
+ break;
+
+ case GLIBTOP_FILE_TYPE_INET6SOCKET:
+ case GLIBTOP_FILE_TYPE_INETSOCKET:
+ object = friendlier_hostname(openfiles->info.sock.dest_host,
+ openfiles->info.sock.dest_port);
+ break;
+
+ case GLIBTOP_FILE_TYPE_LOCALSOCKET:
+ object = g_strdup(openfiles->info.localsock.name);
+ break;
+
+ default:
+ object = g_strdup("");
+ }
+
+ gtk_list_store_insert (GTK_LIST_STORE (model), &row, 0);
+ gtk_list_store_set (GTK_LIST_STORE (model), &row,
+ COL_FD, openfiles->fd,
+ COL_TYPE, get_type_name(static_cast<glibtop_file_type>(openfiles->type)),
+ COL_OBJECT, object,
+ COL_OPENFILE_STRUCT, g_memdup(openfiles, sizeof(*openfiles)),
+ -1);
+
+ g_free(object);
+}
+
+static GList *old_maps = NULL;
+
+static gboolean
+classify_openfiles (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ GHashTable *new_maps = static_cast<GHashTable*>(data);
+ GtkTreeIter *old_iter;
+ glibtop_open_files_entry *openfiles;
+ gchar *old_name;
+
+ gtk_tree_model_get (model, iter, 1, &old_name, -1);
+
+ openfiles = static_cast<glibtop_open_files_entry*>(g_hash_table_lookup (new_maps, old_name));
+ if (openfiles) {
+ g_hash_table_remove (new_maps, old_name);
+ g_free (old_name);
+ return FALSE;
+
+ }
+
+ old_iter = gtk_tree_iter_copy (iter);
+ old_maps = g_list_append (old_maps, old_iter);
+ g_free (old_name);
+ return FALSE;
+
+}
+
+
+static gboolean
+compare_open_files(gconstpointer a, gconstpointer b)
+{
+ const glibtop_open_files_entry *o1 = static_cast<const glibtop_open_files_entry *>(a);
+ const glibtop_open_files_entry *o2 = static_cast<const glibtop_open_files_entry *>(b);
+
+ /* Falta manejar los diferentes tipos! */
+ return (o1->fd == o2->fd) && (o1->type == o2->type); /* XXX! */
+}
+
+
+static void
+update_openfiles_dialog (GsmTreeView *tree)
+{
+ ProcInfo *info;
+ GtkTreeModel *model;
+ glibtop_open_files_entry *openfiles;
+ glibtop_proc_open_files procmap;
+ GHashTable *new_maps;
+ guint i;
+
+ pid_t pid = GPOINTER_TO_UINT(static_cast<pid_t*>(g_object_get_data (G_OBJECT (tree), "selected_info")));
+ info = GsmApplication::get()->processes.find(pid);
+
+
+ if (!info)
+ return;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+
+ openfiles = glibtop_get_proc_open_files (&procmap, info->pid);
+
+ if (!openfiles)
+ return;
+
+ new_maps = static_cast<GHashTable *>(g_hash_table_new_full (g_str_hash, compare_open_files,
+ NULL, NULL));
+ for (i=0; i < procmap.number; i++)
+ g_hash_table_insert (new_maps, openfiles + i, openfiles + i);
+
+ gtk_tree_model_foreach (model, classify_openfiles, new_maps);
+
+ g_hash_table_foreach (new_maps, add_new_files, model);
+
+ while (old_maps) {
+ GtkTreeIter *iter = static_cast<GtkTreeIter*>(old_maps->data);
+ glibtop_open_files_entry *openfiles = NULL;
+
+ gtk_tree_model_get (model, iter,
+ COL_OPENFILE_STRUCT, &openfiles,
+ -1);
+
+ gtk_list_store_remove (GTK_LIST_STORE (model), iter);
+ gtk_tree_iter_free (iter);
+ g_free (openfiles);
+
+ old_maps = g_list_next (old_maps);
+
+ }
+
+ g_hash_table_destroy (new_maps);
+ g_free (openfiles);
+}
+
+static void
+close_openfiles_dialog (GtkDialog *dialog, gint id, gpointer data)
+{
+ GsmTreeView *tree = static_cast<GsmTreeView*>(data);
+ guint timer;
+
+ gsm_tree_view_save_state (tree);
+
+ timer = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (tree), "timer"));
+ g_source_remove (timer);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ return ;
+}
+
+
+static GsmTreeView *
+create_openfiles_tree (GsmApplication *app)
+{
+ GsmTreeView *tree;
+ GtkListStore *model;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ gint i;
+
+ const gchar * const titles[] = {
+ /* Translators: "FD" here means "File Descriptor". Please use
+ a very short translation if possible, and at most
+ 2-3 characters for it to be able to fit in the UI. */
+ N_("FD"),
+ N_("Type"),
+ N_("Object")
+ };
+
+ model = gtk_list_store_new (NUM_OPENFILES_COL,
+ G_TYPE_INT, /* FD */
+ G_TYPE_STRING, /* Type */
+ G_TYPE_STRING, /* Object */
+ G_TYPE_POINTER /* open_files_entry */
+ );
+
+ auto settings = g_settings_get_child (app->settings->gobj (), GSM_SETTINGS_CHILD_OPEN_FILES);
+
+ tree = gsm_tree_view_new (settings, FALSE);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tree), GTK_TREE_MODEL (model));
+ g_object_unref (G_OBJECT (model));
+
+ for (i = 0; i < NUM_OPENFILES_COL-1; i++) {
+ cell = gtk_cell_renderer_text_new ();
+
+ switch (i) {
+ case COL_FD:
+ g_object_set(cell, "xalign", 1.0f, NULL);
+ break;
+ }
+
+ column = gtk_tree_view_column_new_with_attributes (_(titles[i]),
+ cell,
+ "text", i,
+ NULL);
+ gtk_tree_view_column_set_sort_column_id (column, i);
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
+ }
+
+ gsm_tree_view_load_state (GSM_TREE_VIEW (tree));
+
+ return tree;
+
+}
+
+
+static gboolean
+openfiles_timer (gpointer data)
+{
+ GsmTreeView* tree = static_cast<GsmTreeView*>(data);
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+ g_assert(model);
+
+ update_openfiles_dialog (tree);
+
+ return TRUE;
+}
+
+
+static void
+create_single_openfiles_dialog (GtkTreeModel *model, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ GsmApplication *app = static_cast<GsmApplication *>(data);
+ GtkDialog *openfilesdialog;
+ GtkGrid *cmd_grid;
+ GtkLabel *label;
+ GtkScrolledWindow *scrolled;
+ GsmTreeView *tree;
+ ProcInfo *info;
+ guint timer;
+
+ gtk_tree_model_get (model, iter, COL_POINTER, &info, -1);
+
+ if (!info)
+ return;
+
+ GtkBuilder *builder = gtk_builder_new();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/openfiles.ui", NULL);
+
+ openfilesdialog = GTK_DIALOG (gtk_builder_get_object (builder, "openfiles_dialog"));
+
+ cmd_grid = GTK_GRID (gtk_builder_get_object (builder, "cmd_grid"));
+
+
+ label = procman_make_label_for_mmaps_or_ofiles (
+ _("_Files opened by process “%s” (PID %u):"),
+ info->name.c_str(),
+ info->pid);
+
+ gtk_container_add (GTK_CONTAINER (cmd_grid), GTK_WIDGET (label));
+
+ scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "scrolled"));
+
+ tree = create_openfiles_tree (app);
+ gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (tree));
+ g_object_set_data (G_OBJECT (tree), "selected_info", GUINT_TO_POINTER (info->pid));
+
+ g_signal_connect (G_OBJECT (openfilesdialog), "response",
+ G_CALLBACK (close_openfiles_dialog), tree);
+
+ gtk_builder_connect_signals (builder, NULL);
+
+ gtk_window_set_transient_for (GTK_WINDOW (openfilesdialog), GTK_WINDOW (GsmApplication::get()->main_window));
+ gtk_widget_show_all (GTK_WIDGET (openfilesdialog));
+
+ timer = g_timeout_add_seconds (5, openfiles_timer, tree);
+ g_object_set_data (G_OBJECT (tree), "timer", GUINT_TO_POINTER (timer));
+
+ update_openfiles_dialog (tree);
+
+ g_object_unref (G_OBJECT (builder));
+}
+
+
+void
+create_openfiles_dialog (GsmApplication *app)
+{
+ gtk_tree_selection_selected_foreach (app->selection, create_single_openfiles_dialog,
+ app);
+}
diff --git a/src/openfiles.h b/src/openfiles.h
new file mode 100644
index 0000000..9595e8d
--- /dev/null
+++ b/src/openfiles.h
@@ -0,0 +1,11 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_OPENFILES_H_
+#define _GSM_OPENFILES_H_
+
+#include <glib.h>
+
+#include "application.h"
+
+void create_openfiles_dialog (GsmApplication *app);
+
+#endif /* _GSM_OPENFILES_H_ */
diff --git a/src/org.gnome.gnome-system-monitor.gschema.xml.in b/src/org.gnome.gnome-system-monitor.gschema.xml.in
new file mode 100644
index 0000000..ee620ab
--- /dev/null
+++ b/src/org.gnome.gnome-system-monitor.gschema.xml.in
@@ -0,0 +1,776 @@
+<schemalist gettext-domain="@GETTEXT_PACKAGE@">
+ <schema id="org.gnome.gnome-system-monitor" path="/org/gnome/gnome-system-monitor/">
+ <key name="window-state" type="(iiii)">
+ <default>(700, 500, 50, 50)</default>
+ <summary>Main window size and position in the form (width, height, xpos, ypos)</summary>
+ </key>
+
+ <key name="maximized" type="b">
+ <default>false
+ </default>
+ <summary>Main Window should open maximized
+ </summary>
+ </key>
+
+ <key name="show-dependencies" type="b">
+ <default>false
+ </default>
+ <summary>Show process dependencies in tree form
+ </summary>
+ </key>
+
+ <key name="solaris-mode" type="b">
+ <default>true
+ </default>
+ <summary>Solaris mode for CPU percentage
+ </summary>
+ <description>If TRUE, system-monitor operates in “Solaris mode” where a task’s CPU usage is divided by the total number of CPUs. Otherwise, it operates in “Irix mode”.
+ </description>
+ </key>
+
+ <key name="process-memory-in-iec" type="b">
+ <default>false
+ </default>
+ <summary>Show memory in IEC
+ </summary>
+ </key>
+
+ <key name="smooth-refresh" type="b">
+ <default>true
+ </default>
+ <summary>Enable/Disable smooth refresh
+ </summary>
+ </key>
+
+ <key name="kill-dialog" type="b">
+ <default>true
+ </default>
+ <summary>Show warning dialog when killing processes
+ </summary>
+ </key>
+
+ <key name="update-interval" type="i">
+ <range min="1000" max="100000"/>
+ <default>3000</default>
+ <summary>Time in milliseconds between updates of the process view</summary>
+ </key>
+
+ <key name="graph-update-interval" type="i">
+ <range min="50" max="10000"/>
+ <default>100</default>
+ <summary>Time in milliseconds between updates of the graphs</summary>
+ </key>
+
+ <key name="show-all-fs" type="b">
+ <default>false
+ </default>
+ <summary>Whether information about all file systems should be displayed
+ </summary>
+ <description>Whether to display information about all file systems (including types like “autofs” and “procfs”). Useful for getting a list of all currently mounted file systems.
+ </description>
+ </key>
+
+ <key name="disks-interval" type="i">
+ <range min="1000" max="100000"/>
+ <default>5000</default>
+ <summary>Time in milliseconds between updates of the devices list</summary>
+ </key>
+
+ <key name="graph-data-points" type="i">
+ <range min="30" max="600"/>
+ <default>60</default>
+ <summary>Time amount of data points in the resource graphs</summary>
+ </key>
+
+ <key name="show-whose-processes" type="s">
+ <default>'user'</default>
+ <choices>
+ <choice value="all"/>
+ <choice value="user"/>
+ <choice value="active"/>
+ </choices>
+ <summary>Determines which processes to show.</summary>
+ </key>
+
+ <key name="current-tab" type="s">
+ <choices>
+ <choice value="processes"/>
+ <choice value="resources"/>
+ <choice value="disks"/>
+ </choices>
+ <default>'processes'</default>
+ <summary>Saves the currently viewed tab
+ </summary>
+ </key>
+
+ <key name="cpu-colors" type="a(us)">
+ <default>[
+ (0, '#e6194B'),
+ (1, '#f58231'),
+ (2, '#ffe119'),
+ (3, '#bfef45'),
+ (4, '#3cb44b'),
+ (5, '#42d4f4'),
+ (6, '#4363d8'),
+ (7, '#911eb4'),
+ (8, '#f032e6'),
+ (9, '#fabebe'),
+ (10,'#ffd8b1'),
+ (11,'#fffac8'),
+ (12,'#aaffc3'),
+ (13,'#469990'),
+ (14,'#000075'),
+ (15,'#e6beff')
+ ]</default>
+ <summary>CPU colors
+ </summary>
+ <description>Each entry is in the format (CPU#, Hexadecimal color value)
+ </description>
+ </key>
+
+ <key name="mem-color" type="s">
+ <default>'#AB1852'
+ </default>
+ <summary>Default graph memory color
+ </summary>
+ </key>
+
+ <key name="swap-color" type="s">
+ <default>'#49A835'
+ </default>
+ <summary>Default graph swap color
+ </summary>
+ </key>
+
+ <key name="net-in-color" type="s">
+ <default>'#2D7DB3'
+ </default>
+ <summary>Default graph incoming network traffic color
+ </summary>
+ </key>
+
+ <key name="net-out-color" type="s">
+ <default>'#EE1D00'
+ </default>
+ <summary>Default graph outgoing network traffic color
+ </summary>
+ </key>
+
+ <key name="network-in-bits" type="b">
+ <default>false
+ </default>
+ <summary>Show network traffic in bits
+ </summary>
+ </key>
+
+ <key name="network-total-unit" type="b">
+ <default>false
+ </default>
+ <summary>Set network totals unit separately
+ </summary>
+ </key>
+
+ <key name="network-total-in-bits" type="b">
+ <default>false
+ </default>
+ <summary>Show network totals in bits
+ </summary>
+ </key>
+
+ <key name="logarithmic-scale" type="b">
+ <default>false
+ </default>
+ <summary>Show memory in logarithmic scale
+ </summary>
+ <description>If TRUE, system-monitor shows the CPU chart as a stacked area chart instead of a line chart.
+ </description>
+ </key>
+
+ <key name="cpu-stacked-area-chart" type="b">
+ <default>false
+ </default>
+ <summary>Show CPU chart as stacked area chart
+ </summary>
+ <description>If TRUE, system-monitor shows the CPU chart as a stacked area chart instead of a line chart.
+ </description>
+ </key>
+
+ <key name="cpu-smooth-graph" type="b">
+ <default>true
+ </default>
+ <summary>Show CPU, Memory, and Network charts as smooth graphs using Bezier curves
+ </summary>
+ <description>If TRUE, system-monitor shows the CPU, Memory, and Network charts as smoothed graphs, otherwise as line charts.
+ </description>
+ </key>
+
+ <key name="resources-memory-in-iec" type="b">
+ <default>false
+ </default>
+ <summary>Show memory and swap in IEC
+ </summary>
+ </key>
+
+ <child name="proctree" schema="org.gnome.gnome-system-monitor.proctree" />
+ <child name="disktreenew" schema="org.gnome.gnome-system-monitor.disktreenew" />
+ <child name="memmapstree" schema="org.gnome.gnome-system-monitor.memmapstree" />
+ <child name="openfilestree" schema="org.gnome.gnome-system-monitor.openfilestree" />
+ </schema>
+
+ <schema id="org.gnome.gnome-system-monitor.proctree" path="/org/gnome/gnome-system-monitor/proctree/">
+ <key name="sort-col" type="i">
+ <default>0
+ </default>
+ <summary>Process view sort column
+ </summary>
+ </key>
+
+ <key name="columns-order" type="ai">
+ <default>[ 0 ]
+ </default>
+ <summary>Process view columns order
+ </summary>
+ </key>
+
+ <key name="sort-order" type="i">
+ <default>0
+ </default>
+ <summary>Process view sort order
+ </summary>
+ </key>
+
+ <key name="col-0-width" type="i">
+ <default>225
+ </default>
+ <summary>Width of process “Name” column
+ </summary>
+ </key>
+
+ <key name="col-0-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “Name” column on startup
+ </summary>
+ </key>
+
+ <key name="col-1-width" type="i">
+ <default>98
+ </default>
+ <summary>Width of process “User” column
+ </summary>
+ </key>
+
+ <key name="col-1-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “User” column on startup
+ </summary>
+ </key>
+
+ <key name="col-2-width" type="i">
+ <default>37
+ </default>
+ <summary>Width of process “Status” column
+ </summary>
+ </key>
+
+ <key name="col-2-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Status” column on startup
+ </summary>
+ </key>
+
+ <key name="col-3-width" type="i">
+ <default>90
+ </default>
+ <summary>Width of process “Virtual Memory” column
+ </summary>
+ </key>
+
+ <key name="col-3-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Virtual Memory” column on startup
+ </summary>
+ </key>
+
+ <key name="col-4-width" type="i">
+ <default>90
+ </default>
+ <summary>Width of process “Resident Memory” column
+ </summary>
+ </key>
+
+ <key name="col-4-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Resident Memory” column on startup
+ </summary>
+ </key>
+
+ <key name="col-5-width" type="i">
+ <default>90
+ </default>
+ <summary>Width of process “Writable Memory” column
+ </summary>
+ </key>
+
+ <key name="col-5-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Writable Memory” column on startup
+ </summary>
+ </key>
+
+ <key name="col-6-width" type="i">
+ <default>90
+ </default>
+ <summary>Width of process “Shared Memory” column
+ </summary>
+ </key>
+
+ <key name="col-6-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Shared Memory” column on startup
+ </summary>
+ </key>
+
+ <key name="col-7-width" type="i">
+ <default>90
+ </default>
+ <summary>Width of process “X Server Memory” column
+ </summary>
+ </key>
+
+ <key name="col-7-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “X Server Memory” column on startup
+ </summary>
+ </key>
+
+ <key name="col-8-width" type="i">
+ <default>71
+ </default>
+ <summary>Width of process “CPU %” column
+ </summary>
+ </key>
+
+ <key name="col-8-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “CPU %” column on startup
+ </summary>
+ </key>
+
+ <key name="col-9-width" type="i">
+ <default>80
+ </default>
+ <summary>Width of process “CPU Time” column
+ </summary>
+ </key>
+
+ <key name="col-9-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “CPU Time” column on startup
+ </summary>
+ </key>
+
+ <key name="col-10-width" type="i">
+ <default>70
+ </default>
+ <summary>Width of process “Started” column
+ </summary>
+ </key>
+
+ <key name="col-10-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Started” column on startup
+ </summary>
+ </key>
+
+ <key name="col-11-width" type="i">
+ <default>48
+ </default>
+ <summary>Width of process “Nice” column
+ </summary>
+ </key>
+
+ <key name="col-11-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Nice” column on startup
+ </summary>
+ </key>
+
+ <key name="col-12-width" type="i">
+ <default>60
+ </default>
+ <summary>Width of process “ID” column
+ </summary>
+ </key>
+
+ <key name="col-12-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “ID” column on startup
+ </summary>
+ </key>
+
+ <key name="col-13-width" type="i">
+ <default>80
+ </default>
+ <summary>Width of process “SELinux Security Context” column
+ </summary>
+ </key>
+ <key name="col-13-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “SELinux Security Context” column on startup
+ </summary>
+ </key>
+
+ <key name="col-14-width" type="i">
+ <default>120
+ </default>
+ <summary>Width of process “Command Line” column
+ </summary>
+ </key>
+
+ <key name="col-14-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Command Line” column on startup
+ </summary>
+ </key>
+
+ <key name="col-15-width" type="i">
+ <default>80
+ </default>
+ <summary>Width of process “Memory” column
+ </summary>
+ </key>
+
+ <key name="col-15-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “Memory” column on startup
+ </summary>
+ </key>
+
+ <key name="col-16-width" type="i">
+ <default>48
+ </default>
+ <summary>Width of process “Waiting Channel” column
+ </summary>
+ </key>
+
+ <key name="col-16-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Waiting Channel” column on startup
+ </summary>
+ </key>
+
+ <key name="col-17-width" type="i">
+ <default>48
+ </default>
+ <summary>Width of process “Control Group” column
+ </summary>
+ </key>
+
+ <key name="col-17-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Control Group” column on startup
+ </summary>
+ </key>
+
+ <key name="col-18-width" type="i">
+ <default>70
+ </default>
+ <summary>Width of process “Unit” column
+ </summary>
+ </key>
+
+ <key name="col-18-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Unit” column on startup
+ </summary>
+ </key>
+
+ <key name="col-19-width" type="i">
+ <default>41
+ </default>
+ <summary>Width of process “Session” column
+ </summary>
+ </key>
+
+ <key name="col-19-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Session” column on startup
+ </summary>
+ </key>
+
+ <key name="col-20-width" type="i">
+ <default>59
+ </default>
+ <summary>Width of process “Seat” column
+ </summary>
+ </key>
+
+ <key name="col-20-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Seat” column on startup
+ </summary>
+ </key>
+
+ <key name="col-21-width" type="i">
+ <default>59
+ </default>
+ <summary>Width of process “Owner” column
+ </summary>
+ </key>
+
+ <key name="col-21-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show process “Owner” column on startup
+ </summary>
+ </key>
+
+ <key name="col-22-width" type="i">
+ <default>100
+ </default>
+ <summary>Width of process “Total disk read” column
+ </summary>
+ </key>
+
+ <key name="col-22-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “Total disk read” column on startup
+ </summary>
+ </key>
+
+ <key name="col-23-width" type="i">
+ <default>100
+ </default>
+ <summary>Width of process “Total disk write” column
+ </summary>
+ </key>
+
+ <key name="col-23-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “Total disk write” column on startup
+ </summary>
+ </key>
+
+ <key name="col-24-width" type="i">
+ <default>100
+ </default>
+ <summary>Width of process “Disk read” column
+ </summary>
+ </key>
+
+ <key name="col-24-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “Disk read” column on startup
+ </summary>
+ </key>
+
+ <key name="col-25-width" type="i">
+ <default>100
+ </default>
+ <summary>Width of process “Disk write” column
+ </summary>
+ </key>
+
+ <key name="col-25-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “Disk write” column on startup
+ </summary>
+ </key>
+
+ <key name="col-26-width" type="i">
+ <default>100
+ </default>
+ <summary>Width of process “Priority” column
+ </summary>
+ </key>
+
+ <key name="col-26-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show process “Priority” column on startup
+ </summary>
+ </key>
+
+
+ </schema>
+
+ <schema id="org.gnome.gnome-system-monitor.disktreenew" path="/org/gnome/gnome-system-monitor/disktreenew/">
+ <key name="sort-col" type="i">
+ <default>1
+ </default>
+ <summary>Disk view sort column
+ </summary>
+ </key>
+
+ <key name="sort-order" type="i">
+ <default>0
+ </default>
+ <summary>Disk view sort order
+ </summary>
+ </key>
+
+ <key name="columns-order" type="ai">
+ <default>[ 0 ]
+ </default>
+ <summary>Disk view columns order
+ </summary>
+ </key>
+
+ <key name="col-0-width" type="i">
+ <default>100
+ </default>
+ <summary>Width of disk view “Device” column
+ </summary>
+ </key>
+
+ <key name="col-0-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show disk view “Device” column on startup
+ </summary>
+ </key>
+
+ <key name="col-1-width" type="i">
+ <default>100
+ </default>
+ <summary>Width of disk view “Directory” column
+ </summary>
+ </key>
+
+ <key name="col-1-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show disk view “Directory” column on startup
+ </summary>
+ </key>
+
+ <key name="col-2-width" type="i">
+ <default>50
+ </default>
+ <summary>Width of disk view “Type” column
+ </summary>
+ </key>
+
+ <key name="col-2-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show disk view “Type” column on startup
+ </summary>
+ </key>
+
+ <key name="col-3-width" type="i">
+ <default>80
+ </default>
+ <summary>Width of disk view “Total” column
+ </summary>
+ </key>
+
+ <key name="col-3-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show disk view “Total” column on startup
+ </summary>
+ </key>
+
+ <key name="col-4-width" type="i">
+ <default>80
+ </default>
+ <summary>Width of disk view “Free” column
+ </summary>
+ </key>
+
+ <key name="col-4-visible" type="b">
+ <default>false
+ </default>
+ <summary>Show disk view “Free” column on startup
+ </summary>
+ </key>
+
+ <key name="col-5-width" type="i">
+ <default>80
+ </default>
+ <summary>Width of disk view “Available” column
+ </summary>
+ </key>
+
+ <key name="col-5-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show disk view “Available” column on startup
+ </summary>
+ </key>
+
+ <key name="col-6-width" type="i">
+ <default>300
+ </default>
+ <summary>Width of disk view “Used” column
+ </summary>
+ </key>
+
+ <key name="col-6-visible" type="b">
+ <default>true
+ </default>
+ <summary>Show disk view “Used” column on startup
+ </summary>
+ </key>
+
+ </schema>
+
+ <schema id="org.gnome.gnome-system-monitor.memmapstree" path="/org/gnome/gnome-system-monitor/memmapstree/">
+ <key name="sort-col" type="i">
+ <default>0
+ </default>
+ <summary>Memory map sort column
+ </summary>
+ </key>
+
+ <key name="sort-order" type="i">
+ <default>0
+ </default>
+ <summary>Memory map sort order
+ </summary>
+ </key>
+ </schema>
+
+ <schema id="org.gnome.gnome-system-monitor.openfilestree" path="/org/gnome/gnome-system-monitor/openfilestree/">
+ <key name="sort-col" type="i">
+ <default>0
+ </default>
+ <summary>Open files sort column
+ </summary>
+ </key>
+
+ <key name="sort-order" type="i">
+ <default>0
+ </default>
+ <summary>Open files sort order
+ </summary>
+ </key>
+ </schema>
+</schemalist>
+
diff --git a/src/prefsdialog.cpp b/src/prefsdialog.cpp
new file mode 100644
index 0000000..39ca384
--- /dev/null
+++ b/src/prefsdialog.cpp
@@ -0,0 +1,361 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "prefsdialog.h"
+
+#include "cgroups.h"
+#include "proctable.h"
+#include "selinux.h"
+#include "settings-keys.h"
+#include "systemd.h"
+#include "util.h"
+
+static HdyPreferencesWindow *prefs_dialog = NULL;
+
+static gboolean
+prefs_dialog_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ prefs_dialog = NULL;
+ return FALSE;
+}
+
+class SpinButtonUpdater
+{
+public:
+ SpinButtonUpdater(const string& key)
+ : key(key)
+ { }
+
+ static gboolean callback(GtkWidget *widget, GdkEventFocus *event, gpointer data)
+ {
+ SpinButtonUpdater* updater = static_cast<SpinButtonUpdater*>(data);
+ gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
+ updater->update(GTK_SPIN_BUTTON(widget));
+ return FALSE;
+ }
+
+private:
+
+ void update(GtkSpinButton* spin)
+ {
+ int new_value = 1000 * gtk_spin_button_get_value(spin);
+
+ GsmApplication::get()->settings->set_int(this->key, new_value);
+
+ procman_debug("set %s to %d", this->key.c_str(), new_value);
+ }
+
+ const string key;
+};
+
+class ScaleUpdater
+{
+public:
+ ScaleUpdater(const string& key)
+ : key(key)
+ { }
+
+ static gboolean callback(GtkRange *range, gpointer data)
+ {
+ ScaleUpdater* updater = static_cast<ScaleUpdater*>(data);
+ updater->update(range);
+ return FALSE;
+ }
+
+private:
+
+ void update(GtkRange* range)
+ {
+ int new_value = gtk_range_get_value(range);
+
+ GsmApplication::get()->settings->set_int(this->key, new_value);
+
+ procman_debug("set %s to %d", this->key.c_str(), new_value);
+ }
+
+ const string key;
+};
+
+static void
+field_toggled (const gchar *gsettings_parent, gchar *path_str, gpointer data)
+{
+ GtkTreeModel *model = static_cast<GtkTreeModel*>(data);
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
+ GtkTreeIter iter;
+ GtkTreeViewColumn *column;
+ gboolean toggled;
+ auto settings = GsmApplication::get()->settings->get_child (gsettings_parent);
+ int id;
+
+ if (!path)
+ return;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ gtk_tree_model_get (model, &iter, 2, &column, -1);
+
+ gtk_tree_model_get (model, &iter, 0, &toggled, -1);
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, !toggled, -1);
+ gtk_tree_view_column_set_visible (column, !toggled);
+
+ id = gtk_tree_view_column_get_sort_column_id (column);
+
+ auto key = Glib::ustring::compose ("col-%1-visible", id);
+ settings->set_boolean (key, !toggled);
+
+ gtk_tree_path_free (path);
+
+}
+
+static void
+field_row_activated ( GtkTreeView *tree, GtkTreePath *path,
+ GtkTreeViewColumn *column, gpointer data)
+{
+ GtkTreeModel * model = gtk_tree_view_get_model (tree);
+ gchar * path_str = gtk_tree_path_to_string (path);
+ field_toggled((gchar*)data, path_str, model );
+ g_free (path_str);
+}
+
+static void
+proc_field_toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
+{
+ field_toggled("proctree", path_str, data);
+}
+
+static void
+disk_field_toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
+{
+ field_toggled("disktreenew", path_str, data);
+}
+
+static void
+create_field_page(GtkBuilder* builder, GtkTreeView *tree, const gchar *widgetname)
+{
+ GtkTreeView *treeview;
+ GList *it, *columns;
+ GtkListStore *model;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ gchar *full_widgetname;
+
+ full_widgetname = g_strdup_printf ("%s_columns", widgetname);
+ treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, full_widgetname));
+ g_free (full_widgetname);
+
+ model = gtk_list_store_new (3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL(model));
+ g_object_unref (G_OBJECT (model));
+
+ column = gtk_tree_view_column_new ();
+
+ cell = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "active", 0,
+ NULL);
+ if(!g_strcmp0(widgetname, "proctree"))
+ g_signal_connect (G_OBJECT (cell), "toggled", G_CALLBACK (proc_field_toggled), model);
+ else if(!g_strcmp0(widgetname, "disktreenew"))
+ g_signal_connect (G_OBJECT (cell), "toggled", G_CALLBACK (disk_field_toggled), model);
+
+ g_signal_connect (G_OBJECT (GTK_TREE_VIEW (treeview)), "row-activated", G_CALLBACK (field_row_activated), (gpointer)widgetname);
+
+ gtk_tree_view_column_set_clickable (column, TRUE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ column = gtk_tree_view_column_new ();
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", 1,
+ NULL);
+
+ gtk_tree_view_column_set_title (column, "Not Shown");
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree));
+
+ for(it = columns; it; it = it->next)
+ {
+ GtkTreeViewColumn *column = static_cast<GtkTreeViewColumn*>(it->data);
+ GtkTreeIter iter;
+ const gchar *title;
+ gboolean visible;
+ gint column_id;
+
+ title = gtk_tree_view_column_get_title (column);
+ if (!title)
+ title = _("Icon");
+
+ column_id = gtk_tree_view_column_get_sort_column_id(column);
+ if ((column_id == COL_CGROUP) && (!cgroups_enabled()))
+ continue;
+ if ((column_id == COL_SECURITYCONTEXT) && (!can_show_security_context_column ()))
+ continue;
+
+ if ((column_id == COL_UNIT ||
+ column_id == COL_SESSION ||
+ column_id == COL_SEAT ||
+ column_id == COL_OWNER)
+ && !procman::systemd_logind_running()
+ )
+ continue;
+
+ visible = gtk_tree_view_column_get_visible (column);
+
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter, 0, visible, 1, title, 2, column,-1);
+ }
+
+ g_list_free(columns);
+
+}
+
+void
+create_preferences_dialog (GsmApplication *app)
+{
+ typedef SpinButtonUpdater SBU;
+
+ static SBU interval_updater("update-interval");
+ static SBU graph_interval_updater("graph-update-interval");
+ static SBU disks_interval_updater("disks-interval");
+ static ScaleUpdater graph_points_updater("graph-data-points");
+
+ GtkAdjustment *adjustment;
+ GtkSpinButton *spin_button;
+ GtkSwitch *check_switch;
+ GtkSwitch *smooth_switch;
+ GtkBuilder *builder;
+ gfloat update;
+
+ if (prefs_dialog)
+ return;
+
+ builder = gtk_builder_new();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/preferences.ui", NULL);
+
+ prefs_dialog = HDY_PREFERENCES_WINDOW (gtk_builder_get_object (builder, "preferences_dialog"));
+
+ spin_button = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "processes_interval_spinner"));
+
+ update = (gfloat) app->config.update_interval;
+ adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(spin_button));
+ gtk_adjustment_configure (adjustment,
+ update / 1000.0,
+ MIN_UPDATE_INTERVAL / 1000,
+ MAX_UPDATE_INTERVAL / 1000,
+ 0.25,
+ 1.0,
+ 0);
+ g_signal_connect (G_OBJECT (spin_button), "focus_out_event",
+ G_CALLBACK (SBU::callback), &interval_updater);
+
+ smooth_switch = GTK_SWITCH (gtk_builder_get_object (builder, "smooth_switch"));
+ g_settings_bind(app->settings->gobj (), SmoothRefresh::KEY.c_str(), smooth_switch, "active", G_SETTINGS_BIND_DEFAULT);
+
+ check_switch = GTK_SWITCH (gtk_builder_get_object (builder, "check_switch"));
+ g_settings_bind (app->settings->gobj (), GSM_SETTING_SHOW_KILL_DIALOG,
+ check_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *solaris_switch = GTK_SWITCH (gtk_builder_get_object (builder, "solaris_switch"));
+ g_settings_bind (app->settings->gobj (), GSM_SETTING_SOLARIS_MODE,
+ solaris_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *proc_mem_in_iec_switch = GTK_SWITCH (gtk_builder_get_object (builder, "proc_mem_in_iec_switch"));
+ g_settings_bind(app->settings->gobj (), GSM_SETTING_PROCESS_MEMORY_IN_IEC,
+ proc_mem_in_iec_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *logarithmic_scale_switch = GTK_SWITCH (gtk_builder_get_object (builder, "logarithmic_scale_switch"));
+ g_settings_bind (app->settings->gobj (), GSM_SETTING_LOGARITHMIC_SCALE,
+ logarithmic_scale_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *draw_stacked_switch = GTK_SWITCH (gtk_builder_get_object (builder, "draw_stacked_switch"));
+ g_settings_bind (app->settings->gobj (), GSM_SETTING_DRAW_STACKED,
+ draw_stacked_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *draw_smooth_switch = GTK_SWITCH (gtk_builder_get_object (builder, "draw_smooth_switch"));
+ g_settings_bind (app->settings->gobj (), GSM_SETTING_DRAW_SMOOTH,
+ draw_smooth_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *res_mem_in_iec_switch = GTK_SWITCH (gtk_builder_get_object (builder, "res_mem_in_iec_switch"));
+ g_settings_bind(app->settings->gobj (), GSM_SETTING_RESOURCES_MEMORY_IN_IEC,
+ res_mem_in_iec_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ create_field_page (builder, GTK_TREE_VIEW (app->tree), "proctree");
+
+ update = (gfloat) app->config.graph_update_interval;
+ spin_button = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "resources_interval_spinner"));
+ adjustment = gtk_spin_button_get_adjustment (spin_button);
+ gtk_adjustment_configure (adjustment, update / 1000.0, 0.05,
+ 10.0, 0.05, 0.5, 0);
+ g_signal_connect (G_OBJECT (spin_button), "focus_out_event",
+ G_CALLBACK(SBU::callback),
+ &graph_interval_updater);
+
+ update = (gfloat) app->config.graph_data_points;
+ GtkRange* range = GTK_RANGE (gtk_builder_get_object (builder, "graph_data_points_scale"));
+ adjustment = gtk_range_get_adjustment (range);
+ gtk_adjustment_configure (adjustment, update, 30,
+ 600, 10, 60, 0);
+ g_signal_connect (G_OBJECT (range), "value-changed",
+ G_CALLBACK(ScaleUpdater::callback),
+ &graph_points_updater);
+
+ GtkSwitch *bits_switch = GTK_SWITCH (gtk_builder_get_object (builder, "bits_switch"));
+ g_settings_bind(app->settings->gobj (), GSM_SETTING_NETWORK_IN_BITS,
+ bits_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *bits_unit_switch = GTK_SWITCH (gtk_builder_get_object (builder, "bits_unit_switch"));
+ g_settings_bind(app->settings->gobj (), GSM_SETTING_NETWORK_TOTAL_UNIT,
+ bits_unit_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ GtkSwitch *bits_total_switch = GTK_SWITCH (gtk_builder_get_object (builder, "bits_total_switch"));
+ g_settings_bind(app->settings->gobj (), GSM_SETTING_NETWORK_TOTAL_IN_BITS,
+ bits_total_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ update = (gfloat) app->config.disks_update_interval;
+ spin_button = GTK_SPIN_BUTTON (gtk_builder_get_object (builder, "devices_interval_spinner"));
+ adjustment = gtk_spin_button_get_adjustment (spin_button);
+ gtk_adjustment_configure (adjustment, update / 1000.0, 1.0,
+ 100.0, 1.0, 1.0, 0);
+ g_signal_connect (G_OBJECT (spin_button), "focus_out_event",
+ G_CALLBACK(SBU::callback),
+ &disks_interval_updater);
+
+
+ check_switch = GTK_SWITCH (gtk_builder_get_object (builder, "all_devices_check"));
+ g_settings_bind (app->settings->gobj (), GSM_SETTING_SHOW_ALL_FS,
+ check_switch, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ create_field_page (builder, GTK_TREE_VIEW (app->disk_list), "disktreenew");
+
+ gtk_window_set_transient_for (GTK_WINDOW (prefs_dialog), GTK_WINDOW (GsmApplication::get()->main_window));
+ gtk_window_set_modal (GTK_WINDOW (prefs_dialog), TRUE);
+
+ gtk_widget_show_all (GTK_WIDGET (prefs_dialog));
+ g_signal_connect (G_OBJECT (prefs_dialog), "delete-event",
+ G_CALLBACK (prefs_dialog_delete_event), NULL);
+
+ gtk_window_present (GTK_WINDOW (prefs_dialog));
+
+ gtk_builder_connect_signals (builder, NULL);
+ g_object_unref (G_OBJECT (builder));
+}
diff --git a/src/prefsdialog.h b/src/prefsdialog.h
new file mode 100644
index 0000000..63c966c
--- /dev/null
+++ b/src/prefsdialog.h
@@ -0,0 +1,10 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_PREFS_DIALOG_H_
+#define _GSM_PREFS_DIALOG_H_
+
+#include "application.h"
+
+void create_preferences_dialog (GsmApplication *app);
+void on_bits_unit_button_toggled (GtkToggleButton *togglebutton, gpointer bits_total_button);
+
+#endif /* _GSM_PREFS_DIALOG_H_ */
diff --git a/src/prettytable.cpp b/src/prettytable.cpp
new file mode 100644
index 0000000..3a04c63
--- /dev/null
+++ b/src/prettytable.cpp
@@ -0,0 +1,337 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#ifdef HAVE_WNCK
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+#endif
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <glibtop/procstate.h>
+#include <giomm/error.h>
+#include <giomm/file.h>
+#include <glibmm/miscutils.h>
+#include <iostream>
+
+#include <vector>
+
+#include "prettytable.h"
+#include "defaulttable.h"
+#include "proctable.h"
+#include "util.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+namespace
+{
+ const unsigned APP_ICON_SIZE = 16;
+}
+
+
+PrettyTable::PrettyTable()
+{
+#ifdef HAVE_WNCK
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ WnckScreen* screen = wnck_screen_get_default();
+ g_signal_connect(G_OBJECT(screen), "application_opened",
+ G_CALLBACK(PrettyTable::on_application_opened), this);
+ g_signal_connect(G_OBJECT(screen), "application_closed",
+ G_CALLBACK(PrettyTable::on_application_closed), this);
+ }
+#endif
+#endif
+
+ // init GIO apps cache
+ std::vector<std::string> dirs = Glib::get_system_data_dirs();
+ for (std::vector<std::string>::iterator it = dirs.begin(); it != dirs.end(); ++it) {
+ std::string path = (*it).append("/applications");
+ Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(path);
+ Glib::RefPtr<Gio::FileMonitor> monitor = file->monitor_directory();
+ monitor->set_rate_limit(1000); // 1 second
+
+ monitor->signal_changed().connect(sigc::mem_fun(this, &PrettyTable::file_monitor_event));
+ monitors[path] = monitor;
+ }
+
+ this->init_gio_app_cache();
+}
+
+
+PrettyTable::~PrettyTable()
+{
+}
+
+#ifdef HAVE_WNCK
+void
+PrettyTable::on_application_opened(WnckScreen* screen, WnckApplication* app, gpointer data)
+{
+ PrettyTable * const that = static_cast<PrettyTable*>(data);
+
+ pid_t pid = wnck_application_get_pid(app);
+
+ if (pid == 0)
+ return;
+
+ const char* icon_name = wnck_application_get_icon_name(app);
+
+
+ Glib::RefPtr<Gdk::Pixbuf> icon;
+
+ icon = Glib::wrap(gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), icon_name, APP_ICON_SIZE, GTK_ICON_LOOKUP_USE_BUILTIN, NULL));
+
+ if (not icon) {
+ icon = Glib::wrap(wnck_application_get_icon(app), /* take_copy */ true);
+ icon = icon->scale_simple(APP_ICON_SIZE, APP_ICON_SIZE, Gdk::INTERP_HYPER);
+ }
+
+ if (not icon)
+ return;
+
+ that->register_application(pid, icon);
+}
+
+
+
+void
+PrettyTable::register_application(pid_t pid, Glib::RefPtr<Gdk::Pixbuf> icon)
+{
+ /* If process already exists then set the icon. Otherwise put into hash
+ ** table to be added later */
+ if (ProcInfo* info = GsmApplication::get()->processes.find(pid))
+ {
+ info->set_icon(icon);
+ // move the ref to the map
+ this->apps[pid] = icon;
+ procman_debug("WNCK OK for %u", unsigned(pid));
+ }
+}
+
+
+
+void
+PrettyTable::on_application_closed(WnckScreen* screen, WnckApplication* app, gpointer data)
+{
+ pid_t pid = wnck_application_get_pid(app);
+
+ if (pid == 0)
+ return;
+
+ static_cast<PrettyTable*>(data)->unregister_application(pid);
+}
+
+
+
+void
+PrettyTable::unregister_application(pid_t pid)
+{
+ IconsForPID::iterator it(this->apps.find(pid));
+
+ if (it != this->apps.end())
+ this->apps.erase(it);
+}
+#endif // HAVE_WNCK
+
+void PrettyTable::init_gio_app_cache ()
+{
+ this->gio_apps.clear();
+
+ Glib::ListHandle<Glib::RefPtr<Gio::AppInfo> > apps = Gio::AppInfo::get_all();
+ for (Glib::ListHandle<Glib::RefPtr<Gio::AppInfo> >::const_iterator it = apps.begin();
+ it != apps.end(); ++it) {
+ Glib::RefPtr<Gio::AppInfo> app = *it;
+ std::string executable = app->get_executable();
+ if (executable != "sh" &&
+ executable != "env")
+ this->gio_apps[executable] = app;
+ }
+}
+
+void PrettyTable::file_monitor_event(Glib::RefPtr<Gio::File>,
+ Glib::RefPtr<Gio::File>,
+ Gio::FileMonitorEvent)
+{
+ this->init_gio_app_cache();
+}
+
+Glib::RefPtr<Gdk::Pixbuf>
+PrettyTable::get_icon_from_theme(const ProcInfo &info)
+{
+ return Glib::wrap(gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), info.name.c_str(), APP_ICON_SIZE, (GtkIconLookupFlags)(GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE), NULL));
+}
+
+
+bool PrettyTable::get_default_icon_name(const string &cmd, string &name)
+{
+ for (size_t i = 0; i != G_N_ELEMENTS(default_table); ++i) {
+ if (default_table[i].command->match(cmd)) {
+ name = default_table[i].icon;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ Try to get an icon from the default_table
+ If it's not in defaults, try to load it.
+ If there is no default for a command, store NULL in defaults
+ so we don't have to lookup again.
+*/
+
+Glib::RefPtr<Gdk::Pixbuf>
+PrettyTable::get_icon_from_default(const ProcInfo &info)
+{
+ Glib::RefPtr<Gdk::Pixbuf> pix;
+ string name;
+
+ if (this->get_default_icon_name(info.name, name)) {
+ IconCache::iterator it(this->defaults.find(name));
+
+ if (it == this->defaults.end()) {
+ pix = Glib::wrap(gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), name.c_str(), APP_ICON_SIZE, (GtkIconLookupFlags)(GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE), NULL));
+ if (pix)
+ this->defaults[name] = pix;
+ } else
+ pix = it->second;
+ }
+
+ return pix;
+}
+
+Glib::RefPtr<Gdk::Pixbuf>
+PrettyTable::get_icon_from_gio(const ProcInfo &info)
+{
+ gchar **cmdline = g_strsplit(info.name.c_str(), " ", 2);
+ const gchar *executable = cmdline[0];
+ Glib::RefPtr<Gdk::Pixbuf> icon;
+
+ if (executable) {
+ Glib::RefPtr<Gio::AppInfo> app = this->gio_apps[executable];
+ Glib::RefPtr<Gio::Icon> gicon;
+ Gtk::IconInfo info;
+
+ if (app)
+ gicon = app->get_icon();
+
+ if (gicon)
+ info = Glib::wrap(gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (), gicon->gobj(), APP_ICON_SIZE, (GtkIconLookupFlags)(GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE)));
+
+ if (info)
+ icon = Glib::wrap(gtk_icon_info_load_icon (info.gobj(), NULL));
+ }
+
+ g_strfreev(cmdline);
+ return icon;
+}
+
+#ifdef HAVE_WNCK
+Glib::RefPtr<Gdk::Pixbuf>
+PrettyTable::get_icon_from_wnck(const ProcInfo &info)
+{
+ Glib::RefPtr<Gdk::Pixbuf> icon;
+
+ IconsForPID::iterator it(this->apps.find(info.pid));
+
+ if (it != this->apps.end())
+ icon = it->second;
+
+ return icon;
+}
+#endif
+
+
+Glib::RefPtr<Gdk::Pixbuf>
+PrettyTable::get_icon_from_name(const ProcInfo &info)
+{
+return Glib::wrap(gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), info.name.c_str(), APP_ICON_SIZE, (GtkIconLookupFlags)(GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE), NULL));
+}
+
+
+Glib::RefPtr<Gdk::Pixbuf>
+PrettyTable::get_icon_dummy(const ProcInfo &)
+{
+ return Glib::wrap(gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), "application-x-executable", APP_ICON_SIZE, GTK_ICON_LOOKUP_USE_BUILTIN, NULL));
+}
+
+
+namespace
+{
+ bool has_kthreadd()
+ {
+ glibtop_proc_state buf;
+ glibtop_get_proc_state(&buf, 2);
+
+ return buf.cmd == string("kthreadd");
+ }
+
+ // @pre: has_kthreadd
+ bool is_kthread(const ProcInfo &info)
+ {
+ return info.pid == 2 or info.ppid == 2;
+ }
+}
+
+
+Glib::RefPtr<Gdk::Pixbuf>
+PrettyTable::get_icon_for_kernel(const ProcInfo &info)
+{
+ if (is_kthread(info))
+ return Glib::wrap(gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), "applications-system", APP_ICON_SIZE, GTK_ICON_LOOKUP_USE_BUILTIN, NULL));
+
+ return Glib::RefPtr<Gdk::Pixbuf>();
+}
+
+
+
+void
+PrettyTable::set_icon(ProcInfo &info)
+{
+ typedef Glib::RefPtr<Gdk::Pixbuf>
+ (PrettyTable::*Getter)(const ProcInfo &);
+
+ static std::vector<Getter> getters;
+
+ if (getters.empty())
+ {
+ getters.push_back(&PrettyTable::get_icon_from_gio);
+#ifdef HAVE_WNCK
+ getters.push_back(&PrettyTable::get_icon_from_wnck);
+#endif
+ getters.push_back(&PrettyTable::get_icon_from_theme);
+ getters.push_back(&PrettyTable::get_icon_from_default);
+ getters.push_back(&PrettyTable::get_icon_from_name);
+ if (has_kthreadd())
+ {
+ procman_debug("kthreadd is running with PID 2");
+ getters.push_back(&PrettyTable::get_icon_for_kernel);
+ }
+ getters.push_back(&PrettyTable::get_icon_dummy);
+ }
+
+ Glib::RefPtr<Gdk::Pixbuf> icon;
+
+ for (size_t i = 0; not icon and i < getters.size(); ++i) {
+ try {
+ icon = (this->*getters[i])(info);
+ }
+ catch (std::exception& e) {
+ g_warning("Failed to load icon for %s(%u) : %s", info.name.c_str(), info.pid, e.what());
+ continue;
+ }
+ catch (Glib::Exception& e) {
+ g_warning("Failed to load icon for %s(%u) : %s", info.name.c_str(), info.pid, e.what().c_str());
+ continue;
+ }
+ }
+
+ info.set_icon(icon);
+}
+
diff --git a/src/prettytable.h b/src/prettytable.h
new file mode 100644
index 0000000..627ac6f
--- /dev/null
+++ b/src/prettytable.h
@@ -0,0 +1,74 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_PRETTY_TABLE_H_
+#define _GSM_PRETTY_TABLE_H_
+
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <glibmm/refptr.h>
+#include <giomm/filemonitor.h>
+
+#include <gdkmm/pixbuf.h>
+
+#include <map>
+#include <string>
+
+#ifdef HAVE_WNCK
+extern "C" {
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+}
+#endif
+
+class ProcInfo;
+
+using std::string;
+
+
+
+class PrettyTable
+{
+public:
+ PrettyTable();
+ ~PrettyTable();
+
+ void set_icon(ProcInfo &);
+
+private:
+
+#ifdef HAVE_WNCK
+ static void on_application_opened(WnckScreen* screen, WnckApplication* app, gpointer data);
+ static void on_application_closed(WnckScreen* screen, WnckApplication* app, gpointer data);
+
+ void register_application(pid_t pid, Glib::RefPtr<Gdk::Pixbuf> icon);
+ void unregister_application(pid_t pid);
+#endif
+
+ Glib::RefPtr<Gdk::Pixbuf> get_icon_from_theme(const ProcInfo &);
+ Glib::RefPtr<Gdk::Pixbuf> get_icon_from_default(const ProcInfo &);
+ Glib::RefPtr<Gdk::Pixbuf> get_icon_from_gio(const ProcInfo &);
+#ifdef HAVE_WNCK
+ Glib::RefPtr<Gdk::Pixbuf> get_icon_from_wnck(const ProcInfo &);
+#endif
+ Glib::RefPtr<Gdk::Pixbuf> get_icon_from_name(const ProcInfo &);
+ Glib::RefPtr<Gdk::Pixbuf> get_icon_for_kernel(const ProcInfo &);
+ Glib::RefPtr<Gdk::Pixbuf> get_icon_dummy(const ProcInfo &);
+
+ bool get_default_icon_name(const string &cmd, string &name);
+ void file_monitor_event (Glib::RefPtr<Gio::File>,
+ Glib::RefPtr<Gio::File>,
+ Gio::FileMonitorEvent);
+ void init_gio_app_cache ();
+
+ typedef std::map<string, Glib::RefPtr<Gdk::Pixbuf> > IconCache;
+ typedef std::map<pid_t, Glib::RefPtr<Gdk::Pixbuf> > IconsForPID;
+ typedef std::map<string, Glib::RefPtr<Gio::AppInfo> > AppCache;
+ typedef std::map<string, Glib::RefPtr<Gio::FileMonitor> > DesktopDirMonitors;
+
+ IconsForPID apps;
+ IconCache defaults;
+ DesktopDirMonitors monitors;
+ AppCache gio_apps;
+};
+
+
+#endif /* _GSM_PRETTY_TABLE_H_ */
diff --git a/src/procactions.cpp b/src/procactions.cpp
new file mode 100644
index 0000000..6868d25
--- /dev/null
+++ b/src/procactions.cpp
@@ -0,0 +1,187 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman process actions
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <config.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include "procactions.h"
+#include "application.h"
+#include "proctable.h"
+#include "procdialogs.h"
+
+
+static void
+renice_single_process (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ const struct ProcActionArgs * const args = static_cast<ProcActionArgs*>(data);
+
+ ProcInfo *info = NULL;
+ gint error;
+ int saved_errno;
+ gchar *error_msg;
+ GtkMessageDialog *dialog;
+
+ gtk_tree_model_get (model, iter, COL_POINTER, &info, -1);
+
+ if (!info)
+ return;
+ if (info->nice == args->arg_value)
+ return;
+ error = setpriority (PRIO_PROCESS, info->pid, args->arg_value);
+
+ /* success */
+ if(error != -1) return;
+
+ saved_errno = errno;
+
+ /* need to be root */
+ if(errno == EPERM || errno == EACCES) {
+ gboolean success;
+
+ success = procdialog_create_root_password_dialog (
+ PROCMAN_ACTION_RENICE, args->app, info->pid,
+ args->arg_value);
+
+ if(success) return;
+
+ if(errno) {
+ saved_errno = errno;
+ }
+ }
+
+ /* failed */
+ error_msg = g_strdup_printf (
+ _("Cannot change the priority of process with PID %d to %d.\n"
+ "%s"),
+ info->pid, args->arg_value, g_strerror(saved_errno));
+
+ dialog = GTK_MESSAGE_DIALOG (gtk_message_dialog_new (
+ NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", error_msg));
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ g_free (error_msg);
+}
+
+
+void
+renice (GsmApplication *app, int nice)
+{
+ struct ProcActionArgs args = { app, nice };
+
+ /* EEEK - ugly hack - make sure the table is not updated as a crash
+ ** occurs if you first kill a process and the tree node is removed while
+ ** still in the foreach function
+ */
+ proctable_freeze (app);
+
+ gtk_tree_selection_selected_foreach(app->selection, renice_single_process,
+ &args);
+
+ proctable_thaw (app);
+
+ proctable_update (app);
+}
+
+
+
+
+static void
+kill_single_process (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ const struct ProcActionArgs * const args = static_cast<ProcActionArgs*>(data);
+ char *error_msg;
+ ProcInfo *info;
+ int error;
+ int saved_errno;
+ GtkMessageDialog *dialog;
+
+ gtk_tree_model_get (model, iter, COL_POINTER, &info, -1);
+
+ if (!info)
+ return;
+
+ error = kill (info->pid, args->arg_value);
+
+ /* success */
+ if(error != -1) return;
+
+ saved_errno = errno;
+
+ /* need to be root */
+ if(errno == EPERM) {
+ gboolean success;
+
+ success = procdialog_create_root_password_dialog (
+ PROCMAN_ACTION_KILL, args->app, info->pid,
+ args->arg_value);
+
+ if(success) return;
+
+ if(errno) {
+ saved_errno = errno;
+ }
+ }
+
+ /* failed */
+ error_msg = g_strdup_printf (
+ _("Cannot kill process with PID %d with signal %d.\n"
+ "%s"),
+ info->pid, args->arg_value, g_strerror(saved_errno));
+
+ dialog = GTK_MESSAGE_DIALOG (gtk_message_dialog_new (
+ NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", error_msg));
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ g_free (error_msg);
+}
+
+
+void
+kill_process (GsmApplication *app, int sig)
+{
+ struct ProcActionArgs args = { app, sig };
+
+ /* EEEK - ugly hack - make sure the table is not updated as a crash
+ ** occurs if you first kill a process and the tree node is removed while
+ ** still in the foreach function
+ */
+ proctable_freeze (app);
+
+ gtk_tree_selection_selected_foreach (app->selection, kill_single_process,
+ &args);
+
+ proctable_thaw (app);
+
+ proctable_update (app);
+}
diff --git a/src/procactions.h b/src/procactions.h
new file mode 100644
index 0000000..f1f20db
--- /dev/null
+++ b/src/procactions.h
@@ -0,0 +1,33 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman process actions
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef _GSM_PROCACTIONS_H_
+#define _GSM_PROCACTIONS_H_
+
+#include "application.h"
+
+void renice (GsmApplication *app, int nice);
+void kill_process (GsmApplication *app, int sig);
+
+struct ProcActionArgs
+{
+ GsmApplication *app;
+ int arg_value;
+};
+
+#endif /* _GSM_PROCACTIONS_H_ */
diff --git a/src/procdialogs.cpp b/src/procdialogs.cpp
new file mode 100644
index 0000000..b0d2a54
--- /dev/null
+++ b/src/procdialogs.cpp
@@ -0,0 +1,336 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman - dialogs
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "procdialogs.h"
+#include "proctable.h"
+#include "prettytable.h"
+#include "procactions.h"
+#include "util.h"
+#include "gsm_gnomesu.h"
+#include "gsm_gksu.h"
+#include "gsm_pkexec.h"
+#include "cgroups.h"
+
+static GtkDialog *renice_dialog = NULL;
+static gint new_nice_value = 0;
+
+
+static void
+kill_dialog_button_pressed (GtkDialog *dialog, gint id, gpointer data)
+{
+ struct ProcActionArgs *kargs = static_cast<ProcActionArgs*>(data);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (id == GTK_RESPONSE_OK)
+ kill_process (kargs->app, kargs->arg_value);
+
+ proctable_thaw (kargs->app);
+ proctable_update (kargs->app);
+ g_free (kargs);
+}
+
+void
+procdialog_create_kill_dialog (GsmApplication *app, int signal)
+{
+ GtkMessageDialog *kill_alert_dialog;
+ GtkWidget *confirm_button;
+
+ gchar *primary, *secondary, *button_text;
+ struct ProcActionArgs *kargs;
+
+ proctable_freeze (app);
+ kargs = g_new(ProcActionArgs, 1);
+ kargs->app = app;
+ kargs->arg_value = signal;
+ gint selected_count = gtk_tree_selection_count_selected_rows (app->selection);
+
+ if ( selected_count == 1 ) {
+ ProcInfo *selected_process = NULL;
+ // get the last selected row
+ gtk_tree_selection_selected_foreach (app->selection, get_last_selected,
+ &selected_process);
+
+ std::string *process_name = &selected_process->name;
+ std::string short_process_name = process_name->substr(0, process_name->find(" -"));
+
+ switch (signal) {
+ case SIGKILL:
+ /*xgettext: primary alert message for killing single process*/
+ primary = g_strdup_printf (_("Are you sure you want to kill the selected process “%s” (PID: %u)?"),
+ short_process_name.c_str(),
+ selected_process->pid);
+ break;
+ case SIGTERM:
+ /*xgettext: primary alert message for ending single process*/
+ primary = g_strdup_printf (_("Are you sure you want to end the selected process “%s” (PID: %u)?"),
+ short_process_name.c_str(),
+ selected_process->pid);
+ break;
+ default: // SIGSTOP
+ /*xgettext: primary alert message for stopping single process*/
+ primary = g_strdup_printf (_("Are you sure you want to stop the selected process “%s” (PID: %u)?"),
+ short_process_name.c_str(),
+ selected_process->pid);
+ break;
+ }
+ } else {
+ switch (signal) {
+ case SIGKILL:
+ /*xgettext: primary alert message for killing multiple processes*/
+ primary = g_strdup_printf (ngettext("Are you sure you want to kill the selected process?",
+ "Are you sure you want to kill the %d selected processes?", selected_count),
+ selected_count);
+ break;
+ case SIGTERM:
+ /*xgettext: primary alert message for ending multiple processes*/
+ primary = g_strdup_printf (ngettext("Are you sure you want to end the selected process?",
+ "Are you sure you want to end the %d selected processes?", selected_count),
+ selected_count);
+ break;
+ default: // SIGSTOP
+ /*xgettext: primary alert message for stopping multiple processes*/
+ primary = g_strdup_printf (ngettext("Are you sure you want to stop the selected process?",
+ "Are you sure you want to stop the %d selected processes?", selected_count),
+ selected_count);
+ break;
+ }
+ }
+
+ switch (signal) {
+ case SIGKILL:
+ /*xgettext: secondary alert message*/
+ secondary = _("Killing a process may destroy data, break the "
+ "session or introduce a security risk. "
+ "Only unresponsive processes should be killed.");
+ button_text = ngettext("_Kill Process", "_Kill Processes", selected_count);
+ break;
+ case SIGTERM:
+ /*xgettext: secondary alert message*/
+ secondary = _("Ending a process may destroy data, break the "
+ "session or introduce a security risk. "
+ "Only unresponsive processes should be ended.");
+ button_text = ngettext("_End Process", "_End Processes", selected_count);
+ break;
+ default: // SIGSTOP
+ /*xgettext: secondary alert message*/
+ secondary = _("Stopping a process may destroy data, break the "
+ "session or introduce a security risk. "
+ "Only unresponsive processes should be stopped.");
+ button_text = ngettext("_Stop Process", "_Stop Processes", selected_count);
+ break;
+ }
+
+ kill_alert_dialog = GTK_MESSAGE_DIALOG (gtk_message_dialog_new (GTK_WINDOW (app->main_window),
+ static_cast<GtkDialogFlags>(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ "%s",
+ primary));
+ g_free (primary);
+
+ gtk_message_dialog_format_secondary_text (kill_alert_dialog,
+ "%s",
+ secondary);
+
+ gtk_dialog_add_button (GTK_DIALOG (kill_alert_dialog),
+ _("_Cancel"), GTK_RESPONSE_CANCEL);
+
+ confirm_button = gtk_dialog_add_button (GTK_DIALOG (kill_alert_dialog),
+ button_text, GTK_RESPONSE_OK);
+ gtk_style_context_add_class (gtk_widget_get_style_context (confirm_button),
+ GTK_STYLE_CLASS_DESTRUCTIVE_ACTION);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (kill_alert_dialog),
+ GTK_RESPONSE_CANCEL);
+
+ g_signal_connect (G_OBJECT (kill_alert_dialog), "response",
+ G_CALLBACK (kill_dialog_button_pressed), kargs);
+
+ gtk_widget_show_all (GTK_WIDGET (kill_alert_dialog));
+}
+
+static void
+renice_scale_changed (GtkAdjustment *adj, gpointer data)
+{
+ GtkLabel *label = GTK_LABEL (data);
+
+ new_nice_value = int(gtk_adjustment_get_value (adj));
+ gchar* text = g_strdup(procman::get_nice_level_with_priority (new_nice_value));
+ gtk_label_set_text (label, text);
+ g_free(text);
+
+}
+
+static void
+renice_dialog_button_pressed (GtkDialog *dialog, gint id, gpointer data)
+{
+ GsmApplication *app = static_cast<GsmApplication *>(data);
+ if (id == 100) {
+ if (new_nice_value == -100)
+ return;
+ renice(app, new_nice_value);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ renice_dialog = NULL;
+}
+
+void
+procdialog_create_renice_dialog (GsmApplication *app)
+{
+ ProcInfo *info;
+
+ GtkLabel *label;
+ GtkLabel *priority_label;
+ GtkAdjustment *renice_adj;
+ GtkBuilder *builder;
+ gchar *text;
+ gchar *dialog_title;
+
+ if (renice_dialog)
+ return;
+
+ gtk_tree_selection_selected_foreach (app->selection, get_last_selected,
+ &info);
+ gint selected_count = gtk_tree_selection_count_selected_rows (app->selection);
+ if (!info)
+ return;
+
+ builder = gtk_builder_new();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/renice.ui", NULL);
+
+ renice_dialog = GTK_DIALOG (gtk_builder_get_object (builder, "renice_dialog"));
+ if ( selected_count == 1 ) {
+ dialog_title = g_strdup_printf (_("Change Priority of Process “%s” (PID: %u)"),
+ info->name.c_str(), info->pid);
+ } else {
+ dialog_title = g_strdup_printf (ngettext("Change Priority of the selected process", "Change Priority of %d selected processes", selected_count),
+ selected_count);
+ }
+
+ gtk_window_set_title (GTK_WINDOW(renice_dialog), dialog_title);
+
+ g_free (dialog_title);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (renice_dialog), 100);
+ new_nice_value = -100;
+
+ renice_adj = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "renice_adj"));
+ gtk_adjustment_configure( GTK_ADJUSTMENT(renice_adj), info->nice, RENICE_VAL_MIN, RENICE_VAL_MAX, 1, 1, 0);
+
+ new_nice_value = 0;
+
+ priority_label = GTK_LABEL (gtk_builder_get_object (builder, "priority_label"));
+ gtk_label_set_label (priority_label, procman::get_nice_level_with_priority (info->nice));
+
+ text = g_strconcat("<small><i><b>", _("Note:"), "</b> ",
+ _("The priority of a process is given by its nice value. A lower nice value corresponds to a higher priority."),
+ "</i></small>", NULL);
+ label = GTK_LABEL (gtk_builder_get_object (builder, "note_label"));
+ gtk_label_set_label (label, _(text));
+ gtk_label_set_line_wrap (label, TRUE);
+ g_free (text);
+
+ g_signal_connect (G_OBJECT (renice_dialog), "response",
+ G_CALLBACK (renice_dialog_button_pressed), app);
+ g_signal_connect (G_OBJECT (renice_adj), "value_changed",
+ G_CALLBACK (renice_scale_changed), priority_label);
+
+ gtk_window_set_transient_for (GTK_WINDOW (renice_dialog), GTK_WINDOW (GsmApplication::get()->main_window));
+ gtk_widget_show_all (GTK_WIDGET (renice_dialog));
+
+ gtk_builder_connect_signals (builder, NULL);
+
+ g_object_unref (G_OBJECT (builder));
+}
+
+
+
+static char *
+procman_action_to_command(ProcmanActionType type,
+ gint pid,
+ gint extra_value)
+{
+ switch (type) {
+ case PROCMAN_ACTION_KILL:
+ return g_strdup_printf("kill -s %d %d", extra_value, pid);
+ case PROCMAN_ACTION_RENICE:
+ return g_strdup_printf("renice %d %d", extra_value, pid);
+ default:
+ g_assert_not_reached();
+ }
+}
+
+
+gboolean
+multi_root_check (char *command)
+{
+ if (procman_has_pkexec ()) {
+ return gsm_pkexec_create_root_password_dialog (command);
+ }
+
+ if (procman_has_gksu ()) {
+ return gsm_gksu_create_root_password_dialog (command);
+ }
+
+ if (procman_has_gnomesu ()) {
+ return gsm_gnomesu_create_root_password_dialog (command);
+ }
+
+ return FALSE;
+}
+
+/*
+ * type determines whether if dialog is for killing process or renice.
+ * type == PROCMAN_ACTION_KILL, extra_value -> signal to send
+ * type == PROCMAN_ACTION_RENICE, extra_value -> new priority.
+ */
+gboolean
+procdialog_create_root_password_dialog(ProcmanActionType type,
+ GsmApplication *app,
+ gint pid,
+ gint extra_value)
+{
+ char * command;
+ gboolean ret = FALSE;
+
+ command = procman_action_to_command(type, pid, extra_value);
+
+ procman_debug("Trying to run '%s' as root", command);
+
+ if (procman_has_pkexec())
+ ret = gsm_pkexec_create_root_password_dialog(command);
+ else if (procman_has_gksu())
+ ret = gsm_gksu_create_root_password_dialog(command);
+ else if (procman_has_gnomesu())
+ ret = gsm_gnomesu_create_root_password_dialog(command);
+
+ g_free(command);
+ return ret;
+}
diff --git a/src/procdialogs.h b/src/procdialogs.h
new file mode 100644
index 0000000..93fad17
--- /dev/null
+++ b/src/procdialogs.h
@@ -0,0 +1,53 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman - dialogs
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef _GSM_PROCDIALOGS_H_
+#define _GSM_PROCDIALOGS_H_
+
+
+#include <glib.h>
+#include "application.h"
+
+/* These are the actual range of settable values. Values outside this range
+ are scaled back to these limits. So show these limits in the slider
+*/
+#ifdef __linux__
+const int RENICE_VAL_MIN = -20;
+const int RENICE_VAL_MAX = 19;
+#else /* ! linux */
+const int RENICE_VAL_MIN = -20;
+const int RENICE_VAL_MAX = 20;
+#endif
+
+
+typedef enum
+{
+ PROCMAN_ACTION_RENICE,
+ PROCMAN_ACTION_KILL
+} ProcmanActionType;
+
+
+void procdialog_create_kill_dialog (GsmApplication *app, int signal);
+void procdialog_create_renice_dialog (GsmApplication *app);
+gboolean multi_root_check (char *command);
+gboolean procdialog_create_root_password_dialog (ProcmanActionType type,
+ GsmApplication *app,
+ gint pid, gint extra_value);
+void procdialog_create_memmaps_dialog (GsmApplication *app);
+
+#endif /* _GSM_PROCDIALOGS_H_ */
diff --git a/src/procproperties.cpp b/src/procproperties.cpp
new file mode 100644
index 0000000..7476448
--- /dev/null
+++ b/src/procproperties.cpp
@@ -0,0 +1,251 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Process properties dialog
+ * Copyright (C) 2010 Krishnan Parthasarathi <krishnan.parthasarathi@gmail.com>
+ * Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <glibtop/procmem.h>
+#include <glibtop/procmap.h>
+#include <glibtop/procstate.h>
+
+#include "application.h"
+#include "procproperties.h"
+#include "proctable.h"
+#include "util.h"
+#include "legacy/e_date.h"
+
+enum
+{
+ COL_PROP = 0,
+ COL_VAL,
+ NUM_COLS,
+};
+
+typedef struct _proc_arg {
+ const gchar *prop;
+ gchar *val;
+} proc_arg;
+
+static gchar*
+format_memsize(guint64 size)
+{
+ if (size == 0)
+ return g_strdup(_("N/A"));
+ else
+ return g_format_size_full(size, G_FORMAT_SIZE_IEC_UNITS);
+}
+
+static void
+fill_proc_properties (GtkTreeView *tree, ProcInfo *info)
+{
+ guint i;
+ GtkListStore *store;
+
+ if (!info)
+ return;
+
+ get_process_memory_writable (info);
+
+ proc_arg proc_props[] = {
+ { N_("Process Name"), g_strdup_printf("%s", info->name.c_str())},
+ { N_("User"), g_strdup_printf("%s (%d)", info->user.c_str(), info->uid)},
+ { N_("Status"), g_strdup(format_process_state(info->status))},
+ { N_("Memory"), format_memsize(info->mem)},
+ { N_("Virtual Memory"), format_memsize(info->vmsize)},
+ { N_("Resident Memory"), format_memsize(info->memres)},
+ { N_("Writable Memory"), format_memsize(info->memwritable)},
+ { N_("Shared Memory"), format_memsize(info->memshared)},
+#ifdef HAVE_WNCK
+ { N_("X Server Memory"), format_memsize(info->memxserver)},
+#endif
+ { N_("CPU"), g_strdup_printf("%.2f%%", info->pcpu)},
+ { N_("CPU Time"), procman::format_duration_for_display(100 * info->cpu_time / GsmApplication::get()->frequency) },
+ { N_("Started"), procman_format_date_for_display(info->start_time) },
+ { N_("Nice"), g_strdup_printf("%d", info->nice)},
+ { N_("Priority"), g_strdup_printf("%s", procman::get_nice_level(info->nice)) },
+ { N_("ID"), g_strdup_printf("%d", info->pid)},
+ { N_("Security Context"), not info->security_context.empty()?g_strdup_printf("%s", info->security_context.c_str()):g_strdup(_("N/A"))},
+ { N_("Command Line"), g_strdup_printf("%s", info->arguments.c_str())},
+ { N_("Waiting Channel"), g_strdup_printf("%s", info->wchan.c_str())},
+ { N_("Control Group"), not info->cgroup_name.empty()?g_strdup_printf("%s", info->cgroup_name.c_str()):g_strdup(_("N/A"))},
+ { NULL, NULL}
+ };
+
+ store = GTK_LIST_STORE(gtk_tree_view_get_model(tree));
+ for (i = 0; proc_props[i].prop; i++) {
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i)) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, COL_PROP, gettext(proc_props[i].prop), -1);
+ }
+
+ gtk_list_store_set(store, &iter, COL_VAL, g_strstrip(proc_props[i].val), -1);
+ g_free(proc_props[i].val);
+ }
+}
+
+static void
+update_procproperties_dialog (GtkTreeView *tree)
+{
+ ProcInfo *info;
+
+ pid_t pid = GPOINTER_TO_UINT(static_cast<pid_t*>(g_object_get_data (G_OBJECT (tree), "selected_info")));
+ info = GsmApplication::get()->processes.find(pid);
+
+ fill_proc_properties(tree, info);
+}
+
+static void
+close_procprop_dialog (GtkDialog *dialog, gint id, gpointer data)
+{
+ GtkTreeView *tree = static_cast<GtkTreeView*>(data);
+ guint timer;
+
+ timer = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (tree), "timer"));
+ g_source_remove (timer);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static GtkTreeView *
+create_procproperties_tree (GsmApplication *app, ProcInfo *info)
+{
+ GtkTreeView *tree;
+ GtkListStore *model;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ gint i;
+
+ model = gtk_list_store_new (NUM_COLS,
+ G_TYPE_STRING, /* Property */
+ G_TYPE_STRING /* Value */
+ );
+
+ tree = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)));
+ g_object_unref (G_OBJECT (model));
+
+ for (i = 0; i < NUM_COLS; i++) {
+ cell = gtk_cell_renderer_text_new ();
+
+ column = gtk_tree_view_column_new_with_attributes (NULL,
+ cell,
+ "text", i,
+ NULL);
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_append_column (tree, column);
+ }
+
+ gtk_tree_view_set_headers_visible (tree, FALSE);
+ fill_proc_properties(tree, info);
+
+ return tree;
+}
+
+static gboolean
+procprop_timer (gpointer data)
+{
+ GtkTreeView *tree = static_cast<GtkTreeView*>(data);
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (tree);
+ g_assert(model);
+
+ update_procproperties_dialog (tree);
+
+ return TRUE;
+}
+
+static void
+create_single_procproperties_dialog (GtkTreeModel *model, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ GsmApplication *app = static_cast<GsmApplication *>(data);
+
+ GtkDialog *procpropdialog;
+ GtkBox *dialog_vbox, *vbox;
+ GtkBox *cmd_hbox;
+ gchar *label;
+ GtkScrolledWindow *scrolled;
+ GtkTreeView *tree;
+ ProcInfo *info;
+ guint timer;
+
+ gtk_tree_model_get (model, iter, COL_POINTER, &info, -1);
+
+ if (!info)
+ return;
+
+ procpropdialog = GTK_DIALOG (g_object_new (GTK_TYPE_DIALOG,
+ "use-header-bar", TRUE, NULL));
+
+ label = g_strdup_printf( _("%s (PID %u)"), info->name.c_str(), info->pid);
+ gtk_window_set_title (GTK_WINDOW (procpropdialog), label);
+ g_free (label);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (procpropdialog), TRUE);
+
+ gtk_window_set_resizable (GTK_WINDOW (procpropdialog), TRUE);
+ gtk_window_set_default_size (GTK_WINDOW (procpropdialog), 575, 400);
+ gtk_container_set_border_width (GTK_CONTAINER (procpropdialog), 5);
+
+ vbox = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (procpropdialog)));
+ gtk_box_set_spacing (vbox, 2);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
+
+ dialog_vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
+ gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), 5);
+ gtk_box_pack_start (vbox, GTK_WIDGET (dialog_vbox), TRUE, TRUE, 0);
+
+ cmd_hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12));
+ gtk_box_pack_start (dialog_vbox, GTK_WIDGET (cmd_hbox), FALSE, FALSE, 0);
+
+ scrolled = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+ gtk_scrolled_window_set_policy (scrolled,
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (scrolled,
+ GTK_SHADOW_IN);
+
+ tree = create_procproperties_tree (app, info);
+ gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (tree));
+ g_object_set_data (G_OBJECT (tree), "selected_info", GUINT_TO_POINTER (info->pid));
+
+ gtk_box_pack_start (dialog_vbox, GTK_WIDGET (scrolled), TRUE, TRUE, 0);
+ gtk_widget_show_all (GTK_WIDGET (scrolled));
+
+ g_signal_connect (G_OBJECT (procpropdialog), "response",
+ G_CALLBACK (close_procprop_dialog), tree);
+
+ gtk_window_set_transient_for (GTK_WINDOW (procpropdialog), GTK_WINDOW (GsmApplication::get()->main_window));
+ gtk_widget_show_all (GTK_WIDGET (procpropdialog));
+
+ timer = g_timeout_add_seconds (5, procprop_timer, tree);
+ g_object_set_data (G_OBJECT (tree), "timer", GUINT_TO_POINTER (timer));
+
+ update_procproperties_dialog (tree);
+}
+
+void
+create_procproperties_dialog (GsmApplication *app)
+{
+ gtk_tree_selection_selected_foreach (app->selection, create_single_procproperties_dialog,
+ app);
+}
diff --git a/src/procproperties.h b/src/procproperties.h
new file mode 100644
index 0000000..da04ae9
--- /dev/null
+++ b/src/procproperties.h
@@ -0,0 +1,30 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Process properties dialog
+ * Copyright (C) 2010 Krishnan Parthasarathi <krishnan.parthasarathi@gmail.com>
+ * Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _GSM_PROCPROPERTIES_H_
+#define _GSM_PROCPROPERTIES_H_
+
+#include <glib.h>
+
+#include "application.h"
+
+void create_procproperties_dialog (GsmApplication *app);
+
+#endif /* _GSM_PROCPROPERTIES_H_ */
diff --git a/src/proctable.cpp b/src/proctable.cpp
new file mode 100644
index 0000000..078750c
--- /dev/null
+++ b/src/proctable.cpp
@@ -0,0 +1,1280 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman tree view and process updating
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <config.h>
+
+
+#include <string.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <glibtop.h>
+#include <glibtop/proclist.h>
+#include <glibtop/procstate.h>
+#include <glibtop/procio.h>
+#include <glibtop/procmem.h>
+#include <glibtop/procmap.h>
+#include <glibtop/proctime.h>
+#include <glibtop/procuid.h>
+#include <glibtop/procargs.h>
+#include <glibtop/prockernel.h>
+#include <glibtop/mem.h>
+#include <glibtop/swap.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <time.h>
+
+#include <set>
+#include <list>
+
+#ifdef HAVE_WNCK
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+#endif
+
+#include "application.h"
+#include "proctable.h"
+#include "prettytable.h"
+#include "util.h"
+#include "interface.h"
+#include "selinux.h"
+#include "settings-keys.h"
+#include "cgroups.h"
+#include "legacy/treeview.h"
+#include "systemd.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+ProcInfo* ProcList::find(pid_t pid)
+{
+ auto it = data.find(pid);
+ return (it == data.end() ? nullptr : &it->second);
+}
+
+static void
+cb_save_tree_state(gpointer, gpointer data)
+{
+ GsmApplication * const app = static_cast<GsmApplication *>(data);
+
+ gsm_tree_view_save_state (app->tree);
+}
+
+static void
+cb_proctree_destroying (GtkTreeView *self, gpointer data)
+{
+ g_signal_handlers_disconnect_by_func (self,
+ (gpointer) cb_save_tree_state,
+ data);
+
+ g_signal_handlers_disconnect_by_func (gtk_tree_view_get_model (self),
+ (gpointer) cb_save_tree_state,
+ data);
+}
+
+static gboolean
+cb_tree_button_pressed (GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ GtkTreePath *path;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+
+ if (!gdk_event_triggers_context_menu ((GdkEvent *) event))
+ return FALSE;
+
+ if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (app->tree), event->x, event->y, &path, NULL, NULL, NULL))
+ return FALSE;
+
+ if (!gtk_tree_selection_path_is_selected (selection, path)) {
+ if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ }
+
+ gtk_tree_path_free (path);
+
+ gtk_menu_popup_at_pointer (GTK_MENU (app->popup_menu), NULL);
+ return TRUE;
+}
+
+static gboolean
+cb_tree_popup_menu (GtkWidget *widget, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ gtk_menu_popup_at_pointer (GTK_MENU (app->popup_menu), NULL);
+
+ return TRUE;
+}
+
+void
+get_last_selected (GtkTreeModel *model, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ ProcInfo **info = (ProcInfo**) data;
+
+ gtk_tree_model_get (model, iter, COL_POINTER, info, -1);
+}
+
+static void
+cb_row_selected (GtkTreeSelection *selection, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ ProcInfo *selected_process = NULL;
+ gint selected_count = gtk_tree_selection_count_selected_rows (selection);
+
+ app->selection = selection;
+
+ gchar *button_text = ngettext("_End Process", "_End Processes", selected_count);
+ gtk_button_set_label (GTK_BUTTON(app->end_process_button), button_text);
+
+ /* get the most recent selected process and determine if there are
+ ** no selected processes
+ */
+ gtk_tree_selection_selected_foreach (selection, get_last_selected,
+ &selected_process);
+ if (selected_process) {
+ GVariant *priority;
+ gint nice = selected_process->nice;
+ if (nice < -7)
+ priority = g_variant_new_int32 (-20);
+ else if (nice < -2)
+ priority = g_variant_new_int32 (-5);
+ else if (nice < 3)
+ priority = g_variant_new_int32 (0);
+ else if (nice < 7)
+ priority = g_variant_new_int32 (5);
+ else
+ priority = g_variant_new_int32 (19);
+
+ GAction *action = g_action_map_lookup_action (G_ACTION_MAP (app->main_window),
+ "priority");
+
+ g_action_change_state (action, priority);
+ }
+ update_sensitivity(app);
+}
+
+static gint
+cb_timeout (gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+ guint new_interval;
+
+ proctable_update (app);
+
+ if (app->smooth_refresh->get(new_interval)) {
+ app->timeout = g_timeout_add(new_interval,
+ cb_timeout,
+ app);
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+cb_refresh_icons (GtkIconTheme *theme, gpointer data)
+{
+ GsmApplication *app = (GsmApplication *) data;
+
+ for (auto& v : app->processes) {
+ app->pretty_table->set_icon(v.second);
+ }
+
+ proctable_update (app);
+}
+
+static gboolean
+iter_matches_search_key (GtkTreeModel *model, GtkTreeIter *iter, const gchar *search_text)
+{
+ char *name;
+ char *user;
+ pid_t pid;
+ char *pids;
+ char *args;
+ gboolean found;
+ char *search_pattern;
+ char **keys;
+ Glib::RefPtr<Glib::Regex> regex;
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &name,
+ COL_USER, &user,
+ COL_PID, &pid,
+ COL_ARGS, &args,
+ -1);
+
+ pids = g_strdup_printf ("%d", pid);
+
+ keys = g_strsplit_set(search_text, " |", -1);
+ search_pattern = g_strjoinv ("|", keys);
+ try {
+ regex = Glib::Regex::create(search_pattern, Glib::REGEX_CASELESS);
+ } catch (const Glib::Error& ex) {
+ regex = Glib::Regex::create(Glib::Regex::escape_string(search_pattern), Glib::REGEX_CASELESS);
+ }
+
+ found = (name && regex->match(name)) || (user && regex->match(user))
+ || (pids && regex->match(pids)) || (args && regex->match(args));
+
+ g_strfreev (keys);
+ g_free (search_pattern);
+ g_free (name);
+ g_free (user);
+ g_free (args);
+ g_free (pids);
+
+ return found;
+}
+
+static gboolean
+process_visibility_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ GsmApplication * const app = static_cast<GsmApplication *>(data);
+ const gchar * search_text = app->search_entry == NULL ? "" : gtk_entry_get_text (GTK_ENTRY (app->search_entry));
+ GtkTreePath *tree_path = gtk_tree_model_get_path (model, iter);
+
+ if (strcmp (search_text, "") == 0) {
+ gtk_tree_path_free (tree_path);
+ return TRUE;
+ }
+
+ // in case we are in dependencies view, we show (and expand) rows not matching the text, but having a matching child
+ gboolean match = false;
+ if (app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)) {
+ GtkTreeIter child;
+ if (gtk_tree_model_iter_children (model, &child, iter)) {
+ gboolean child_match = FALSE;
+ do {
+ child_match = process_visibility_func (model, &child, data);
+ } while (gtk_tree_model_iter_next (model, &child) && !child_match);
+ match = child_match;
+ }
+
+ match |= iter_matches_search_key (model, iter, search_text);
+ if (match && (strlen (search_text) > 0)) {
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (app->tree), tree_path);
+ }
+
+ } else {
+ match = iter_matches_search_key (model, iter, search_text);
+ }
+
+ gtk_tree_path_free (tree_path);
+
+ return match;
+}
+
+static void
+proctable_clear_tree (GsmApplication * const app)
+{
+ GtkTreeModel *model;
+
+ model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (
+ gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT (
+ gtk_tree_view_get_model (GTK_TREE_VIEW(app->tree))))));
+
+ gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+ proctable_free_table (app);
+
+ update_sensitivity(app);
+}
+
+static void
+cb_show_dependencies_changed(Gio::Settings& settings, Glib::ustring key, GsmApplication* app) {
+ if (app->timeout) {
+ gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (app->tree),
+ settings.get_boolean (GSM_SETTING_SHOW_DEPENDENCIES));
+
+ proctable_clear_tree (app);
+ proctable_update (app);
+ }
+}
+
+static void
+cb_show_whose_processes_changed(Gio::Settings& settings, Glib::ustring key, GsmApplication* app) {
+ if (app->timeout) {
+ proctable_clear_tree (app);
+ proctable_update (app);
+ }
+}
+
+GsmTreeView *
+proctable_new (GsmApplication * const app)
+{
+ GsmTreeView *proctree;
+ GtkTreeStore *model;
+ GtkTreeModelFilter *model_filter;
+ GtkTreeModelSort *model_sort;
+ GtkTreeSelection *selection;
+
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_renderer;
+ const gchar *titles[] = {
+ N_("Process Name"),
+ N_("User"),
+ N_("Status"),
+ N_("Virtual Memory"),
+ N_("Resident Memory"),
+ N_("Writable Memory"),
+ N_("Shared Memory"),
+ N_("X Server Memory"),
+ /* xgettext:no-c-format */ N_("% CPU"),
+ N_("CPU Time"),
+ N_("Started"),
+ N_("Nice"),
+ N_("ID"),
+ N_("Security Context"),
+ N_("Command Line"),
+ N_("Memory"),
+ /* xgettext: combined noun, the function the process is waiting in, see wchan ps(1) */
+ N_("Waiting Channel"),
+ N_("Control Group"),
+ N_("Unit"),
+ N_("Session"),
+ /* TRANSLATORS: Seat = i.e. the physical seat the session of the process belongs to, only
+ for multi-seat environments. See http://en.wikipedia.org/wiki/Multiseat_configuration */
+ N_("Seat"),
+ N_("Owner"),
+ N_("Disk read total"),
+ N_("Disk write total"),
+ N_("Disk read"),
+ N_("Disk write"),
+ N_("Priority"),
+ NULL,
+ "POINTER"
+ };
+
+ gint i;
+ auto settings = g_settings_get_child (app->settings->gobj (), GSM_SETTINGS_CHILD_PROCESSES);
+ model = gtk_tree_store_new (NUM_COLUMNS,
+ G_TYPE_STRING, /* Process Name */
+ G_TYPE_STRING, /* User */
+ G_TYPE_UINT, /* Status */
+ G_TYPE_ULONG, /* VM Size */
+ G_TYPE_ULONG, /* Resident Memory */
+ G_TYPE_ULONG, /* Writable Memory */
+ G_TYPE_ULONG, /* Shared Memory */
+ G_TYPE_ULONG, /* X Server Memory */
+ G_TYPE_DOUBLE, /* % CPU */
+ G_TYPE_UINT64, /* CPU time */
+ G_TYPE_ULONG, /* Started */
+ G_TYPE_INT, /* Nice */
+ G_TYPE_UINT, /* ID */
+ G_TYPE_STRING, /* Security Context */
+ G_TYPE_STRING, /* Arguments */
+ G_TYPE_ULONG, /* Memory */
+ G_TYPE_STRING, /* wchan */
+ G_TYPE_STRING, /* Cgroup */
+ G_TYPE_STRING, /* Unit */
+ G_TYPE_STRING, /* Session */
+ G_TYPE_STRING, /* Seat */
+ G_TYPE_STRING, /* Owner */
+ G_TYPE_UINT64, /* Disk read total */
+ G_TYPE_UINT64, /* Disk write total*/
+ G_TYPE_UINT64, /* Disk read */
+ G_TYPE_UINT64, /* Disk write */
+ G_TYPE_STRING, /* Priority */
+ GDK_TYPE_PIXBUF, /* Icon */
+ G_TYPE_POINTER, /* ProcInfo */
+ G_TYPE_STRING /* Sexy tooltip */
+ );
+
+ model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (model), NULL));
+
+ gtk_tree_model_filter_set_visible_func(model_filter, process_visibility_func, app, NULL);
+
+ model_sort = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (model_filter)));
+
+ proctree = gsm_tree_view_new (settings, TRUE);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (proctree), GTK_TREE_MODEL (model_sort));
+
+ gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (proctree), COL_TOOLTIP);
+ gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (proctree), app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES));
+ gtk_tree_view_set_enable_search (GTK_TREE_VIEW (proctree), FALSE);
+ g_object_unref (G_OBJECT (model));
+
+ column = gtk_tree_view_column_new ();
+
+ cell_renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell_renderer,
+ "pixbuf", COL_PIXBUF,
+ NULL);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell_renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell_renderer,
+ "text", COL_NAME,
+ NULL);
+ gtk_tree_view_column_set_title (column, _(titles[0]));
+
+ gtk_tree_view_column_set_sort_column_id (column, COL_NAME);
+ gtk_tree_view_column_set_resizable (column, TRUE);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_min_width (column, 1);
+ gtk_tree_view_column_set_reorderable(column, TRUE);
+
+ gsm_tree_view_append_and_bind_column (proctree, column);
+ gtk_tree_view_set_expander_column (GTK_TREE_VIEW (proctree), column);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+
+ for (i = COL_USER; i <= COL_PRIORITY; i++) {
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *cell;
+ PangoAttrList *attrs = NULL;
+
+#ifndef HAVE_WNCK
+ if (i == COL_MEMXSERVER)
+ continue;
+#endif
+
+ if (i == COL_MEMWRITABLE)
+ continue;
+
+ cell = gtk_cell_renderer_text_new();
+ col = gtk_tree_view_column_new();
+ gtk_tree_view_column_pack_start(col, cell, TRUE);
+ gtk_tree_view_column_set_title(col, _(titles[i]));
+ gtk_tree_view_column_set_resizable(col, TRUE);
+ gtk_tree_view_column_set_sort_column_id(col, i);
+ gtk_tree_view_column_set_reorderable(col, TRUE);
+ gsm_tree_view_append_and_bind_column (proctree, col);
+
+ // type
+ switch (i) {
+#ifdef HAVE_WNCK
+ case COL_MEMXSERVER:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::size_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+ break;
+#endif
+ case COL_VMSIZE:
+ case COL_MEMRES:
+ case COL_MEMSHARED:
+ case COL_MEM:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::size_na_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+ break;
+ case COL_CPU:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::percentage_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+ break;
+ case COL_CPU_TIME:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::duration_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+ break;
+
+ case COL_START_TIME:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::time_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+ break;
+
+ case COL_STATUS:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::status_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+ break;
+ case COL_DISK_READ_TOTAL:
+ case COL_DISK_WRITE_TOTAL:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::size_na_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+ break;
+ case COL_DISK_READ_CURRENT:
+ case COL_DISK_WRITE_CURRENT:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::io_rate_cell_data_func,
+ GUINT_TO_POINTER(i),
+ NULL);
+
+ break;
+ case COL_PRIORITY:
+ gtk_tree_view_column_set_cell_data_func(col, cell,
+ &procman::priority_cell_data_func,
+ GUINT_TO_POINTER(COL_NICE),
+ NULL);
+ break;
+ default:
+ gtk_tree_view_column_set_attributes(col, cell, "text", i, NULL);
+ break;
+ }
+
+ // Tabular Numbers
+ switch (i) {
+#ifdef HAVE_WNCK
+ case COL_MEMXSERVER:
+#endif
+ case COL_PID:
+ case COL_VMSIZE:
+ case COL_MEMRES:
+ case COL_MEMSHARED:
+ case COL_MEM:
+ case COL_CPU:
+ case COL_CPU_TIME:
+ case COL_DISK_READ_TOTAL:
+ case COL_DISK_WRITE_TOTAL:
+ case COL_DISK_READ_CURRENT:
+ case COL_DISK_WRITE_CURRENT:
+ case COL_START_TIME:
+ case COL_NICE:
+ case COL_WCHAN:
+ attrs = make_tnum_attr_list ();
+ g_object_set (cell, "attributes", attrs, NULL);
+ g_clear_pointer (&attrs, pango_attr_list_unref);
+ break;
+ default:
+ break;
+ }
+
+ // sorting
+ switch (i) {
+#ifdef HAVE_WNCK
+ case COL_MEMXSERVER:
+#endif
+ case COL_VMSIZE:
+ case COL_MEMRES:
+ case COL_MEMSHARED:
+ case COL_MEM:
+ case COL_CPU:
+ case COL_CPU_TIME:
+ case COL_DISK_READ_TOTAL:
+ case COL_DISK_WRITE_TOTAL:
+ case COL_DISK_READ_CURRENT:
+ case COL_DISK_WRITE_CURRENT:
+ case COL_START_TIME:
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model_sort), i,
+ procman::number_compare_func,
+ GUINT_TO_POINTER (i),
+ NULL);
+ break;
+ case COL_PRIORITY:
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model_sort), i,
+ procman::priority_compare_func,
+ GUINT_TO_POINTER (COL_NICE), NULL);
+ break;
+ default:
+ break;
+ }
+
+ // xalign
+ switch(i)
+ {
+ case COL_VMSIZE:
+ case COL_MEMRES:
+ case COL_MEMSHARED:
+#ifdef HAVE_WNCK
+ case COL_MEMXSERVER:
+#endif
+ case COL_CPU:
+ case COL_NICE:
+ case COL_PID:
+ case COL_DISK_READ_TOTAL:
+ case COL_DISK_WRITE_TOTAL:
+ case COL_DISK_READ_CURRENT:
+ case COL_DISK_WRITE_CURRENT:
+ case COL_CPU_TIME:
+ case COL_MEM:
+ g_object_set(G_OBJECT(cell), "xalign", 1.0f, NULL);
+ break;
+ }
+
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ // sizing
+ switch (i) {
+ case COL_ARGS:
+ gtk_tree_view_column_set_min_width(col, 150);
+ break;
+ default:
+ gtk_tree_view_column_set_min_width(column, 20);
+ break;
+ }
+
+ if (i == COL_ARGS) {
+ gtk_tree_view_column_set_expand(col, TRUE);
+ } else {
+ gtk_tree_view_column_set_expand(col, FALSE);
+ }
+ }
+ app->tree = proctree;
+ app->top_of_tree = NULL;
+ app->last_vscroll_max = 0;
+ app->last_vscroll_value = 0;
+
+ if (!cgroups_enabled ())
+ gsm_tree_view_add_excluded_column (proctree, COL_CGROUP);
+
+ if (!procman::systemd_logind_running())
+ {
+ gsm_tree_view_add_excluded_column (proctree, COL_UNIT);
+ gsm_tree_view_add_excluded_column (proctree, COL_SESSION);
+ gsm_tree_view_add_excluded_column (proctree, COL_SEAT);
+ gsm_tree_view_add_excluded_column (proctree, COL_OWNER);
+ }
+
+ if (!can_show_security_context_column ())
+ gsm_tree_view_add_excluded_column (proctree, COL_SECURITYCONTEXT);
+
+ gsm_tree_view_load_state (proctree);
+
+ GtkIconTheme* theme = gtk_icon_theme_get_default();
+ g_signal_connect(G_OBJECT (theme), "changed", G_CALLBACK (cb_refresh_icons), app);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (proctree));
+ app->selection = selection;
+
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+
+ g_signal_connect (G_OBJECT (selection),
+ "changed",
+ G_CALLBACK (cb_row_selected), app);
+ g_signal_connect (G_OBJECT (proctree), "popup_menu",
+ G_CALLBACK (cb_tree_popup_menu), app);
+ g_signal_connect (G_OBJECT (proctree), "button_press_event",
+ G_CALLBACK (cb_tree_button_pressed), app);
+
+ g_signal_connect (G_OBJECT (proctree), "destroy",
+ G_CALLBACK (cb_proctree_destroying),
+ app);
+
+ g_signal_connect (G_OBJECT (proctree), "columns-changed",
+ G_CALLBACK (cb_save_tree_state), app);
+
+ g_signal_connect (G_OBJECT (model_sort), "sort-column-changed",
+ G_CALLBACK (cb_save_tree_state), app);
+
+ app->settings->signal_changed(GSM_SETTING_SHOW_DEPENDENCIES).connect([app](const Glib::ustring& key) {
+ cb_show_dependencies_changed(*app->settings.operator->(), key, app);
+ });
+
+ app->settings->signal_changed(GSM_SETTING_SHOW_WHOSE_PROCESSES).connect([app](const Glib::ustring& key) {
+ cb_show_whose_processes_changed(*app->settings.operator->(), key, app);
+ });
+
+ gtk_widget_show (GTK_WIDGET (proctree));
+
+ return proctree;
+}
+
+static void
+get_process_name (ProcInfo *info,
+ const gchar *cmd, const GStrv args)
+{
+ if (args) {
+ // look for /usr/bin/very_long_name
+ // and also /usr/bin/interpreter /usr/.../very_long_name
+ // which may have use prctl to alter 'cmd' name
+ for (int i = 0; i != 2 && args[i]; ++i) {
+ char* basename;
+ basename = g_path_get_basename(args[i]);
+
+ if (g_str_has_prefix(basename, cmd)) {
+ info->name = make_string(basename);
+ return;
+ }
+
+ g_free(basename);
+ }
+ }
+ info->name = cmd;
+}
+
+std::string
+ProcInfo::lookup_user(guint uid)
+{
+ static std::map<guint, std::string> users;
+ auto p = users.insert({uid, ""});
+
+ // procman_debug("User lookup for uid %u: %s", uid, (p.second ? "MISS" : "HIT"));
+
+ if (p.second) {
+ struct passwd* pwd;
+ pwd = getpwuid(uid);
+
+ if (pwd && pwd->pw_name)
+ p.first->second = pwd->pw_name;
+ else {
+ char username[16];
+ g_sprintf(username, "%u", uid);
+ p.first->second = username;
+ }
+ }
+
+ return p.first->second;
+}
+
+void
+ProcInfo::set_user(guint uid)
+{
+ if (G_LIKELY(this->uid == uid))
+ return;
+
+ this->uid = uid;
+ this->user = lookup_user(uid);
+}
+
+void
+get_process_memory_writable (ProcInfo *info)
+{
+ glibtop_proc_map buf;
+ glibtop_map_entry *maps;
+
+ maps = glibtop_get_proc_map(&buf, info->pid);
+
+ const bool use_private_dirty = buf.flags & (1 << GLIBTOP_MAP_ENTRY_PRIVATE_DIRTY);
+
+ gulong memwritable = 0;
+ const unsigned number = buf.number;
+
+ for (unsigned i = 0; i < number; ++i) {
+ if (use_private_dirty) {
+ // clang++ 3.4 is not smart enough to move this invariant out of the loop
+ // but who cares ?
+ memwritable += maps[i].private_dirty;
+ }
+ else if (maps[i].perm & GLIBTOP_MAP_PERM_WRITE) {
+ memwritable += maps[i].size;
+ }
+ }
+
+ info->memwritable = memwritable;
+
+ g_free(maps);
+}
+
+static void
+get_process_memory_info(ProcInfo *info)
+{
+ glibtop_proc_mem procmem;
+
+#ifdef HAVE_WNCK
+ info->memxserver = 0;
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
+ WnckResourceUsage xresources;
+
+ wnck_pid_read_resource_usage (gdk_display_get_default (),
+ info->pid,
+ &xresources);
+
+ info->memxserver = xresources.total_bytes_estimate;
+ }
+#endif
+#endif
+
+ glibtop_get_proc_mem(&procmem, info->pid);
+
+ info->vmsize = procmem.vsize;
+ info->memres = procmem.resident;
+ info->memshared = procmem.share;
+
+ info->mem = info->memres - info->memshared;
+#ifdef HAVE_WNCK
+ info->mem += info->memxserver;
+#endif
+}
+
+static void
+update_info_mutable_cols(ProcInfo *info)
+{
+ GtkTreeModel *model;
+ model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (
+ gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(
+ gtk_tree_view_get_model (GTK_TREE_VIEW(GsmApplication::get()->tree))))));
+
+ using procman::tree_store_update;
+
+ tree_store_update(model, &info->node, COL_STATUS, info->status);
+ tree_store_update(model, &info->node, COL_USER, info->user.c_str());
+ tree_store_update(model, &info->node, COL_VMSIZE, info->vmsize);
+ tree_store_update(model, &info->node, COL_MEMRES, info->memres);
+ tree_store_update(model, &info->node, COL_MEMSHARED, info->memshared);
+#ifdef HAVE_WNCK
+ tree_store_update(model, &info->node, COL_MEMXSERVER, info->memxserver);
+#endif
+ tree_store_update(model, &info->node, COL_CPU, info->pcpu);
+ tree_store_update(model, &info->node, COL_CPU_TIME, info->cpu_time);
+ tree_store_update(model, &info->node, COL_DISK_READ_TOTAL, info->disk_read_bytes_total);
+ tree_store_update(model, &info->node, COL_DISK_WRITE_TOTAL, info->disk_write_bytes_total);
+ tree_store_update(model, &info->node, COL_DISK_READ_CURRENT, info->disk_read_bytes_current);
+ tree_store_update(model, &info->node, COL_DISK_WRITE_CURRENT, info->disk_write_bytes_current);
+ tree_store_update(model, &info->node, COL_START_TIME, info->start_time);
+ tree_store_update(model, &info->node, COL_NICE, info->nice);
+ tree_store_update(model, &info->node, COL_MEM, info->mem);
+ tree_store_update(model, &info->node, COL_WCHAN, info->wchan.c_str());
+ tree_store_update(model, &info->node, COL_CGROUP, info->cgroup_name.c_str());
+ tree_store_update(model, &info->node, COL_UNIT, info->unit.c_str());
+ tree_store_update(model, &info->node, COL_SESSION, info->session.c_str());
+ tree_store_update(model, &info->node, COL_SEAT, info->seat.c_str());
+ tree_store_update(model, &info->node, COL_OWNER, info->owner.c_str());
+}
+
+static void
+insert_info_to_tree (ProcInfo *info, GsmApplication *app, bool forced = false)
+{
+ GtkTreeModel *model;
+ GtkTreeModel *filtered;
+ GtkTreeModel *sorted;
+ sorted = gtk_tree_view_get_model (GTK_TREE_VIEW(app->tree));
+ filtered = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(sorted));
+ model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filtered));
+
+ if (app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)) {
+
+ ProcInfo *parent = 0;
+
+ if (not forced)
+ parent = app->processes.find(info->ppid);
+
+ if (parent) {
+ GtkTreePath *parent_node = gtk_tree_model_get_path(model, &parent->node);
+ gtk_tree_store_insert(GTK_TREE_STORE(model), &info->node, &parent->node, 0);
+
+ GtkTreePath *filtered_parent = gtk_tree_model_filter_convert_child_path_to_path (GTK_TREE_MODEL_FILTER (filtered), parent_node);
+ if (filtered_parent != NULL) {
+ GtkTreePath *sorted_parent = gtk_tree_model_sort_convert_child_path_to_path (GTK_TREE_MODEL_SORT (sorted), filtered_parent);
+
+ if (sorted_parent != NULL) {
+ if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(app->tree), sorted_parent)
+#ifdef __linux__
+ // on linuxes we don't want to expand kthreadd by default (always has pid 2)
+ && (parent->pid != 2)
+#endif
+ )
+ gtk_tree_view_expand_row(GTK_TREE_VIEW(app->tree), sorted_parent, FALSE);
+ gtk_tree_path_free (sorted_parent);
+ }
+ gtk_tree_path_free (filtered_parent);
+ }
+ gtk_tree_path_free (parent_node);
+ } else
+ gtk_tree_store_insert(GTK_TREE_STORE(model), &info->node, NULL, 0);
+ }
+ else
+ gtk_tree_store_insert (GTK_TREE_STORE (model), &info->node, NULL, 0);
+
+ gtk_tree_store_set (GTK_TREE_STORE (model), &info->node,
+ COL_POINTER, info,
+ COL_NAME, info->name.c_str(),
+ COL_ARGS, info->arguments.c_str(),
+ COL_TOOLTIP, info->tooltip.c_str(),
+ COL_PID, info->pid,
+ COL_SECURITYCONTEXT, info->security_context.c_str(),
+ -1);
+
+ app->pretty_table->set_icon(*info);
+
+ procman_debug("inserted %d%s", info->pid, (forced ? " (forced)" : ""));
+}
+
+/* Removing a node with children - make sure the children are queued
+** to be readded.
+*/
+template<typename List>
+static void
+remove_info_from_tree (GsmApplication *app, GtkTreeModel *model,
+ ProcInfo& current, List &orphans, unsigned lvl = 0)
+{
+ GtkTreeIter child_node;
+
+ if (std::find(orphans.begin(), orphans.end(), &current) != orphans.end()) {
+ procman_debug("[%u] %d already removed from tree", lvl, int(current.pid));
+ return;
+ }
+
+ procman_debug("[%u] pid %d, %d children", lvl, int(current.pid),
+ gtk_tree_model_iter_n_children(model, &current.node));
+
+ // it is not possible to iterate&erase over a treeview so instead we
+ // just pop one child after another and recursively remove it and
+ // its children
+
+ while (gtk_tree_model_iter_children(model, &child_node, &current.node)) {
+ ProcInfo *child = 0;
+ gtk_tree_model_get(model, &child_node, COL_POINTER, &child, -1);
+ remove_info_from_tree(app, model, *child, orphans, lvl + 1);
+ }
+
+ g_assert(not gtk_tree_model_iter_has_child(model, &current.node));
+
+ orphans.push_back(&current);
+ gtk_tree_store_remove(GTK_TREE_STORE(model), &current.node);
+ procman::poison(current.node, 0x69);
+}
+
+
+static std::string
+get_proc_kernel_wchan(glibtop_proc_kernel& obj) {
+ char buf[40] = {0};
+ g_strlcpy(buf, obj.wchan, sizeof(buf));
+ buf[sizeof(buf)-1] = '\0';
+ return buf;
+}
+
+static void
+update_info (GsmApplication *app, ProcInfo *info)
+{
+ glibtop_proc_state procstate;
+ glibtop_proc_uid procuid;
+ glibtop_proc_time proctime;
+ glibtop_proc_kernel prockernel;
+ glibtop_proc_io procio;
+ gdouble update_interval_seconds = app->config.update_interval / 1000;
+ glibtop_get_proc_kernel(&prockernel, info->pid);
+ info->wchan = get_proc_kernel_wchan(prockernel);
+
+ glibtop_get_proc_state (&procstate, info->pid);
+ info->status = procstate.state;
+
+ glibtop_get_proc_uid (&procuid, info->pid);
+ glibtop_get_proc_time (&proctime, info->pid);
+ glibtop_get_proc_io (&procio, info->pid);
+
+ get_process_memory_info(info);
+
+ info->set_user(procstate.uid);
+
+ // if the cpu time has increased reset the status to running
+ // regardless of kernel state (#606579)
+ guint64 difference = proctime.rtime - info->cpu_time;
+ if (difference > 0)
+ info->status = GLIBTOP_PROCESS_RUNNING;
+
+ guint cpu_scale = 100;
+ if (not app->config.solaris_mode)
+ cpu_scale *= app->config.num_cpus;
+
+ info->pcpu = (gdouble)difference * cpu_scale / app->cpu_total_time;
+ info->pcpu = MIN(info->pcpu, cpu_scale);
+
+ app->processes.cpu_times[info->pid] = info->cpu_time = proctime.rtime;
+ info->nice = procuid.nice;
+
+ info->disk_write_bytes_current = (procio.disk_wbytes - info->disk_write_bytes_total)/update_interval_seconds;
+ info->disk_read_bytes_current = (procio.disk_rbytes - info->disk_read_bytes_total)/update_interval_seconds;
+
+ info->disk_write_bytes_total = procio.disk_wbytes;
+ info->disk_read_bytes_total = procio.disk_rbytes;
+
+ // set the ppid only if one can exist
+ // i.e. pid=0 can never have a parent
+ if (info->pid > 0) {
+ info->ppid = procuid.ppid;
+ }
+
+ g_assert(info->pid != info->ppid);
+ g_assert(info->ppid != -1 || info->pid == 0);
+
+ /* get cgroup data */
+ get_process_cgroup_info(*info);
+
+ procman::get_process_systemd_info(info);
+}
+
+ProcInfo::ProcInfo(pid_t pid)
+ : node(),
+ pixbuf(),
+ pid(pid),
+ ppid(-1),
+ uid(-1)
+{
+ ProcInfo * const info = this;
+ glibtop_proc_state procstate;
+ glibtop_proc_time proctime;
+ glibtop_proc_args procargs;
+ gchar** arguments;
+
+ glibtop_get_proc_state (&procstate, pid);
+ glibtop_get_proc_time (&proctime, pid);
+ arguments = glibtop_get_proc_argv (&procargs, pid, 0);
+
+ /* FIXME : wrong. name and arguments may change with exec* */
+ get_process_name (info, procstate.cmd, static_cast<const GStrv>(arguments));
+
+ std::string tooltip = make_string(g_strjoinv(" ", arguments));
+ if (tooltip.empty())
+ tooltip = procstate.cmd;
+
+ info->tooltip = make_string(g_markup_escape_text(tooltip.c_str(), -1));
+
+ info->arguments = make_string(g_strescape(tooltip.c_str(), "\\\""));
+ g_strfreev(arguments);
+
+ guint64 cpu_time = proctime.rtime;
+ auto app = GsmApplication::get();
+ auto it = app->processes.cpu_times.find(pid);
+ if (it != app->processes.cpu_times.end())
+ {
+ if (proctime.rtime >= it->second)
+ cpu_time = it->second;
+ }
+ info->cpu_time = cpu_time;
+ info->start_time = proctime.start_time;
+
+ get_process_selinux_context (info);
+ get_process_cgroup_info(*info);
+
+ get_process_systemd_info(info);
+}
+
+static void
+refresh_list (GsmApplication *app, const pid_t* pid_list, const guint n)
+{
+ typedef std::list<ProcInfo*> ProcList;
+ ProcList addition;
+
+ GtkTreeModel *model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (
+ gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT (
+ gtk_tree_view_get_model (GTK_TREE_VIEW(app->tree))))));
+ guint i;
+
+ // Add or update processes in the process list
+ for(i = 0; i < n; ++i) {
+ ProcInfo *info = app->processes.find(pid_list[i]);
+
+ if (!info) {
+ info = app->processes.add(pid_list[i]);
+ addition.push_back(info);
+ }
+
+ update_info (app, info);
+ }
+
+
+ // Remove dead processes from the process list and from the
+ // tree. children are queued to be readded at the right place
+ // in the tree.
+
+ const std::set<pid_t> pids(pid_list, pid_list + n);
+
+ auto it = std::begin(app->processes);
+ while (it != std::end(app->processes)) {
+ auto& info = it->second;
+ if (pids.find(info.pid) == pids.end()) {
+ procman_debug("ripping %d", info.pid);
+ remove_info_from_tree(app, model, info, addition);
+ addition.remove(&info);
+ it = app->processes.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // INVARIANT
+ // pid_list == ProcInfo::all + addition
+
+
+ if (app->settings->get_boolean (GSM_SETTING_SHOW_DEPENDENCIES)) {
+
+ // insert process in the tree. walk through the addition list
+ // (new process + process that have a new parent). This loop
+ // handles the dependencies because we cannot insert a process
+ // until its parent is in the tree.
+
+ std::set<pid_t> in_tree(pids);
+
+ for (ProcList::iterator it(addition.begin()); it != addition.end(); ++it)
+ in_tree.erase((*it)->pid);
+
+
+ while (not addition.empty()) {
+ procman_debug("looking for %d parents", int(addition.size()));
+ ProcList::iterator it(addition.begin());
+
+ while (it != addition.end()) {
+ procman_debug("looking for %d's parent with ppid %d",
+ int((*it)->pid), int((*it)->ppid));
+
+
+ // inserts the process in the treeview if :
+ // - it has no parent (ppid = -1),
+ // ie it is for example the [kernel] on FreeBSD
+ // - it is init
+ // - its parent is already in tree
+ // - its parent is unreachable
+ //
+ // rounds == 2 means that addition contains processes with
+ // unreachable parents
+ //
+ // FIXME: this is broken if the unreachable parent becomes active
+ // i.e. it gets active or changes ower
+ // so we just clear the tree on __each__ update
+ // see proctable_update (ProcData * const procdata)
+
+
+ if ((*it)->ppid <= 0 or in_tree.find((*it)->ppid) != in_tree.end()) {
+ insert_info_to_tree(*it, app);
+ in_tree.insert((*it)->pid);
+ it = addition.erase(it);
+ continue;
+ }
+
+ ProcInfo *parent = app->processes.find((*it)->ppid);
+ // if the parent is unreachable
+ if (not parent) {
+ // or std::find(addition.begin(), addition.end(), parent) == addition.end()) {
+ insert_info_to_tree(*it, app, true);
+ in_tree.insert((*it)->pid);
+ it = addition.erase(it);
+ continue;
+ }
+
+ ++it;
+ }
+ }
+ }
+ else {
+ // don't care of the tree
+ for (auto& v : addition) insert_info_to_tree(v, app);
+ }
+
+
+ for (auto& v : app->processes) update_info_mutable_cols(&v.second);
+}
+
+void
+proctable_update (GsmApplication *app)
+{
+ pid_t* pid_list;
+ glibtop_proclist proclist;
+ glibtop_cpu cpu;
+ int which = 0;
+ int arg = 0;
+ auto whose_processes = app->settings->get_string(GSM_SETTING_SHOW_WHOSE_PROCESSES);
+ if (whose_processes == "all") {
+ which = GLIBTOP_KERN_PROC_ALL;
+ arg = 0;
+ } else if (whose_processes == "active") {
+ which = GLIBTOP_KERN_PROC_ALL | GLIBTOP_EXCLUDE_IDLE;
+ arg = 0;
+ } else if (whose_processes == "user") {
+ which = GLIBTOP_KERN_PROC_UID;
+ arg = getuid ();
+ }
+
+ pid_list = glibtop_get_proclist (&proclist, which, arg);
+
+ /* FIXME: total cpu time elapsed should be calculated on an individual basis here
+ ** should probably have a total_time_last gint in the ProcInfo structure */
+ glibtop_get_cpu (&cpu);
+ app->cpu_total_time = MAX(cpu.total - app->cpu_total_time_last, 1);
+ app->cpu_total_time_last = cpu.total;
+
+ // FIXME: not sure if glibtop always returns a sorted list of pid
+ // but it is important otherwise refresh_list won't find the parent
+ std::sort(pid_list, pid_list + proclist.number);
+ refresh_list (app, pid_list, proclist.number);
+
+ // juggling with tree scroll position to fix https://bugzilla.gnome.org/show_bug.cgi?id=92724
+ GtkTreePath* current_top;
+ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(app->tree), 0,0, &current_top, NULL, NULL, NULL)) {
+ GtkAdjustment *vadjustment = GTK_ADJUSTMENT (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (app->tree)));
+ gdouble current_max = gtk_adjustment_get_upper(vadjustment);
+ gdouble current_value = gtk_adjustment_get_value(vadjustment);
+
+ if (app->top_of_tree) {
+ // if the visible cell from the top of the tree is still the same, as last time
+ if (gtk_tree_path_compare (app->top_of_tree, current_top) == 0) {
+ //but something from the scroll parameters has changed compared to the last values
+ if (app->last_vscroll_value == 0 && current_value != 0) {
+ // the tree was scrolled to top, and something has been added above the current top row
+ gtk_tree_view_scroll_to_point(GTK_TREE_VIEW(app->tree), -1, 0);
+ } else if (current_max > app->last_vscroll_max && app->last_vscroll_max == app->last_vscroll_value) {
+ // the tree was scrolled to bottom, something has been added below the current bottom row
+ gtk_tree_view_scroll_to_point(GTK_TREE_VIEW(app->tree), -1, current_max);
+ }
+ }
+
+ gtk_tree_path_free(app->top_of_tree);
+ }
+
+ app->top_of_tree = current_top;
+ app->last_vscroll_value = current_value;
+ app->last_vscroll_max = current_max;
+ }
+
+ g_free (pid_list);
+
+ /* proclist.number == g_list_length(procdata->info) == g_hash_table_size(procdata->pids) */
+}
+
+void
+proctable_free_table (GsmApplication * const app)
+{
+ app->processes.clear();
+}
+
+void
+ProcInfo::set_icon(Glib::RefPtr<Gdk::Pixbuf> icon)
+{
+ this->pixbuf = icon;
+
+ GtkTreeModel *model;
+ model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (
+ gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(
+ gtk_tree_view_get_model (GTK_TREE_VIEW(GsmApplication::get()->tree))))));
+ gtk_tree_store_set(GTK_TREE_STORE(model), &this->node,
+ COL_PIXBUF, (this->pixbuf ? this->pixbuf->gobj() : NULL),
+ -1);
+}
+
+void
+proctable_freeze (GsmApplication *app)
+{
+ if (app->timeout) {
+ g_source_remove (app->timeout);
+ app->timeout = 0;
+ }
+}
+
+void
+proctable_thaw (GsmApplication *app)
+{
+ if (app->timeout)
+ return;
+
+ app->timeout = g_timeout_add (app->config.update_interval,
+ cb_timeout,
+ app);
+}
+
+void
+proctable_reset_timeout (GsmApplication *app)
+{
+ proctable_freeze (app);
+ proctable_thaw (app);
+}
diff --git a/src/proctable.h b/src/proctable.h
new file mode 100644
index 0000000..a66ee93
--- /dev/null
+++ b/src/proctable.h
@@ -0,0 +1,75 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Procman - tree view
+ * Copyright (C) 2001 Kevin Vandersloot
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _GSM_PROCTABLE_H_
+#define _GSM_PROCTABLE_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "application.h"
+#include "legacy/treeview.h"
+
+enum
+{
+ COL_NAME = 0,
+ COL_USER,
+ COL_STATUS,
+ COL_VMSIZE,
+ COL_MEMRES,
+ COL_MEMWRITABLE,
+ COL_MEMSHARED,
+ COL_MEMXSERVER,
+ COL_CPU,
+ COL_CPU_TIME,
+ COL_START_TIME,
+ COL_NICE,
+ COL_PID,
+ COL_SECURITYCONTEXT,
+ COL_ARGS,
+ COL_MEM,
+ COL_WCHAN,
+ COL_CGROUP,
+ COL_UNIT,
+ COL_SESSION,
+ COL_SEAT,
+ COL_OWNER,
+ COL_DISK_READ_TOTAL,
+ COL_DISK_WRITE_TOTAL,
+ COL_DISK_READ_CURRENT,
+ COL_DISK_WRITE_CURRENT,
+ COL_PRIORITY,
+ COL_PIXBUF,
+ COL_POINTER,
+ COL_TOOLTIP,
+ NUM_COLUMNS
+};
+
+
+GsmTreeView* proctable_new (GsmApplication *app);
+void proctable_update (GsmApplication *app);
+void proctable_free_table (GsmApplication *app);
+void proctable_freeze (GsmApplication *app);
+void proctable_thaw (GsmApplication *app);
+void proctable_reset_timeout (GsmApplication *app);
+
+void get_process_memory_writable (ProcInfo *info);
+void get_last_selected (GtkTreeModel *model, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data);
+
+#endif /* _GSM_PROCTABLE_H_ */
diff --git a/src/selinux.cpp b/src/selinux.cpp
new file mode 100644
index 0000000..a9596d5
--- /dev/null
+++ b/src/selinux.cpp
@@ -0,0 +1,66 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glib.h>
+
+#include "selinux.h"
+#include "application.h"
+#include "util.h"
+
+
+static int (*getpidcon)(pid_t, char**);
+static void (*freecon)(char*);
+static int (*is_selinux_enabled)(void);
+
+static gboolean has_selinux;
+
+static gboolean load_selinux(void)
+{
+ return load_symbols("libselinux.so.1",
+ "getpidcon", &getpidcon,
+ "freecon", &freecon,
+ "is_selinux_enabled", &is_selinux_enabled,
+ NULL);
+}
+
+
+
+void
+get_process_selinux_context (ProcInfo *info)
+{
+ char *con;
+
+ if (has_selinux && !getpidcon (info->pid, &con)) {
+ info->security_context = g_strdup (con);
+ freecon (con);
+ }
+}
+
+
+
+gboolean
+can_show_security_context_column (void)
+{
+ if (!(has_selinux = load_selinux()))
+ return FALSE;
+
+ switch (is_selinux_enabled()) {
+ case 1:
+ /* We're running on an SELinux kernel */
+ return TRUE;
+
+ case -1:
+ /* Error; hide the security context column */
+
+ case 0:
+ /* We're not running on an SELinux kernel:
+ hide the security context column */
+
+ default:
+ procman_debug("SELinux was found but is not enabled.\n");
+ return FALSE;
+ }
+}
+
+
+
diff --git a/src/selinux.h b/src/selinux.h
new file mode 100644
index 0000000..e30c7c0
--- /dev/null
+++ b/src/selinux.h
@@ -0,0 +1,15 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_SELINUX_H_
+#define _GSM_SELINUX_H_
+
+#include <glib.h>
+
+#include "application.h"
+
+void
+get_process_selinux_context (ProcInfo *info);
+
+gboolean
+can_show_security_context_column (void) G_GNUC_CONST;
+
+#endif /* _GSM_SELINUX_H_ */
diff --git a/src/setaffinity.cpp b/src/setaffinity.cpp
new file mode 100644
index 0000000..e8c1546
--- /dev/null
+++ b/src/setaffinity.cpp
@@ -0,0 +1,473 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/**
+ * Copyright (C) 2020 Jacob Barkdull
+ *
+ * This program is part of GNOME System Monitor.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+**/
+
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <glibtop/procaffinity.h>
+#include <sys/stat.h>
+#include <glib/gi18n.h>
+
+#include "proctable.h"
+#include "procdialogs.h"
+#include "util.h"
+#include "setaffinity.h"
+
+namespace
+{
+ class SetAffinityData
+ {
+ public:
+ GtkWidget *dialog;
+ pid_t pid;
+ GtkWidget **buttons;
+ guint32 cpu_count;
+ gboolean toggle_single_blocked;
+ gboolean toggle_all_blocked;
+ };
+}
+
+static gboolean
+all_toggled (SetAffinityData *affinity)
+{
+ guint32 i;
+
+ /* Check if any CPU's aren't set for this process */
+ for (i = 1; i <= affinity->cpu_count; i++) {
+ /* If so, return FALSE */
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (affinity->buttons[i]))) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+affinity_toggler_single (GtkToggleButton *button,
+ gpointer data)
+{
+ SetAffinityData *affinity = static_cast<SetAffinityData *>(data);
+ gboolean toggle_all_state = FALSE;
+
+ /* Return void if toggle single is blocked */
+ if (affinity->toggle_single_blocked == TRUE) {
+ return;
+ }
+
+ /* Set toggle all state based on whether all are toggled */
+ if (gtk_toggle_button_get_active (button)) {
+ toggle_all_state = all_toggled (affinity);
+ }
+
+ /* Block toggle all signal */
+ affinity->toggle_all_blocked = TRUE;
+
+ /* Set toggle all check box state */
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (affinity->buttons[0]),
+ toggle_all_state);
+
+ /* Unblock toggle all signal */
+ affinity->toggle_all_blocked = FALSE;
+}
+
+static void
+affinity_toggle_all (GtkToggleButton *toggle_all_button,
+ gpointer data)
+{
+ SetAffinityData *affinity = static_cast<SetAffinityData *>(data);
+
+ guint32 i;
+ gboolean state;
+
+ /* Return void if toggle all is blocked */
+ if (affinity->toggle_all_blocked == TRUE) {
+ return;
+ }
+
+ /* Set individual CPU toggles based on toggle all state */
+ state = gtk_toggle_button_get_active (toggle_all_button);
+
+ /* Block toggle single signal */
+ affinity->toggle_single_blocked = TRUE;
+
+ /* Set all CPU check boxes to specified state */
+ for (i = 1; i <= affinity->cpu_count; i++) {
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (affinity->buttons[i]),
+ state
+ );
+ }
+
+ /* Unblock toggle single signal */
+ affinity->toggle_single_blocked = FALSE;
+}
+
+static void
+set_affinity_error (void)
+{
+ GtkWidget *dialog;
+
+ /* Create error message dialog */
+ dialog = gtk_message_dialog_new (GTK_WINDOW (GsmApplication::get()->main_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "GNU CPU Affinity error: %s",
+ g_strerror (errno));
+
+ /* Set dialog as modal */
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ /* Show the dialog */
+ gtk_widget_show (dialog);
+
+ /* Connect response signal to GTK widget destroy function */
+ g_signal_connect_swapped (dialog,
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ dialog);
+}
+
+static guint16 *
+gsm_set_proc_affinity (glibtop_proc_affinity *buf, GArray *cpus, pid_t pid)
+{
+#ifdef __linux__
+ guint i;
+ cpu_set_t set;
+ guint16 cpu;
+
+ CPU_ZERO (&set);
+
+ for (i = 0; i < cpus->len; i++) {
+ cpu = g_array_index (cpus, guint16, i);
+ CPU_SET (cpu, &set);
+ }
+
+ if (sched_setaffinity (pid, sizeof (set), &set) != -1) {
+ return glibtop_get_proc_affinity (buf, pid);
+ }
+#endif
+
+ return NULL;
+}
+
+static void
+execute_taskset_command (gchar **cpu_list, pid_t pid)
+{
+#ifdef __linux__
+ gchar *pc;
+ gchar *command;
+
+ /* Join CPU number strings by commas for taskset command CPU list */
+ pc = g_strjoinv (",", cpu_list);
+
+ /* Construct taskset command */
+ command = g_strdup_printf ("taskset -pc %s %d", pc, pid);
+
+ /* Execute taskset command; show error on failure */
+ if (!multi_root_check (command)) {
+ set_affinity_error ();
+ }
+
+ /* Free memory for taskset command */
+ g_free (command);
+ g_free (pc);
+#endif
+}
+
+static void
+set_affinity (GtkToggleButton *button,
+ gpointer data)
+{
+ SetAffinityData *affinity = static_cast<SetAffinityData *>(data);
+
+ glibtop_proc_affinity get_affinity;
+ glibtop_proc_affinity set_affinity;
+
+ gchar **cpu_list;
+ GArray *cpuset;
+ guint32 i;
+ gint taskset_cpu = 0;
+
+ /* Create string array for taskset command CPU list */
+ cpu_list = g_new0 (gchar *, affinity->cpu_count);
+
+ /* Check whether we can get process's current affinity */
+ if (glibtop_get_proc_affinity (&get_affinity, affinity->pid) != NULL) {
+ /* If so, create array for CPU numbers */
+ cpuset = g_array_new (FALSE, FALSE, sizeof (guint16));
+
+ /* Run through all CPUs set for this process */
+ for (i = 0; i < affinity->cpu_count; i++) {
+ /* Check if CPU check box button is active */
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (affinity->buttons[i + 1]))) {
+ /* If so, get its CPU number as 16bit integer */
+ guint16 n = i;
+
+ /* Add its CPU for process affinity */
+ g_array_append_val (cpuset, n);
+
+ /* Store CPU number as string for taskset command CPU list */
+ cpu_list[taskset_cpu] = g_strdup_printf ("%i", i);
+ taskset_cpu++;
+ }
+ }
+
+ /* Set process affinity; Show message dialog upon error */
+ if (gsm_set_proc_affinity (&set_affinity, cpuset, affinity->pid) == NULL) {
+ /* If so, check whether an access error occurred */
+ if (errno == EPERM or errno == EACCES) {
+ /* If so, attempt to run taskset as root, show error on failure */
+ execute_taskset_command (cpu_list, affinity->pid);
+ } else {
+ /* If not, show error immediately */
+ set_affinity_error ();
+ }
+ }
+
+ /* Free memory for CPU strings */
+ for (i = 0; i < affinity->cpu_count; i++) {
+ g_free (cpu_list[i]);
+ }
+
+ /* Free CPU array memory */
+ g_array_free (cpuset, TRUE);
+ } else {
+ /* If not, show error message dialog */
+ set_affinity_error ();
+ }
+
+ /* Destroy dialog window */
+ gtk_widget_destroy (affinity->dialog);
+}
+
+static void
+create_single_set_affinity_dialog (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ GsmApplication *app = static_cast<GsmApplication *>(data);
+
+ ProcInfo *info;
+ SetAffinityData *affinity_data;
+ GtkWidget *cancel_button;
+ GtkWidget *apply_button;
+ GtkWidget *dialog_vbox;
+ GtkWidget *label;
+ GtkWidget *scrolled;
+ GtkStyleContext *scrolled_style;
+ GtkGrid *cpulist_grid;
+
+ guint16 *affinity_cpus;
+ guint16 affinity_cpu;
+ glibtop_proc_affinity affinity;
+ guint32 affinity_i;
+ gint button_n;
+ gchar *button_text;
+
+ /* Get selected process information */
+ gtk_tree_model_get (model, iter, COL_POINTER, &info, -1);
+
+ /* Return void if process information comes back not true */
+ if (!info) {
+ return;
+ }
+
+ /* Create affinity data object */
+ affinity_data = new SetAffinityData ();
+
+ /* Set initial check box array */
+ affinity_data->buttons = g_new (GtkWidget *, app->config.num_cpus);
+
+ /* Create dialog window */
+ affinity_data->dialog = GTK_WIDGET (g_object_new (GTK_TYPE_DIALOG,
+ "title", _("Set Affinity"),
+ "use-header-bar", TRUE,
+ "destroy-with-parent", TRUE,
+ NULL));
+
+ /* Add cancel button to header bar */
+ cancel_button = gtk_dialog_add_button (GTK_DIALOG (affinity_data->dialog),
+ _("_Cancel"),
+ GTK_RESPONSE_CANCEL);
+
+ /* Add apply button to header bar */
+ apply_button = gtk_dialog_add_button (GTK_DIALOG (affinity_data->dialog),
+ _("_Apply"),
+ GTK_RESPONSE_APPLY);
+
+ /* Set dialog window "transient for" */
+ gtk_window_set_transient_for (GTK_WINDOW (affinity_data->dialog),
+ GTK_WINDOW (GsmApplication::get()->main_window));
+
+ /* Set dialog window to be resizable */
+ gtk_window_set_resizable (GTK_WINDOW (affinity_data->dialog), TRUE);
+
+ /* Set default dialog window size */
+ gtk_widget_set_size_request (affinity_data->dialog, 600, 430);
+
+ /* Set dialog box padding ("border") */
+ gtk_container_set_border_width (GTK_CONTAINER (affinity_data->dialog), 5);
+
+ /* Get dialog content area VBox */
+ dialog_vbox = gtk_dialog_get_content_area (GTK_DIALOG (affinity_data->dialog));
+
+ /* Set dialog VBox padding ("border") */
+ gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), 10);
+
+ /* Set dialog VBox spacing */
+ gtk_box_set_spacing (GTK_BOX (dialog_vbox), 10);
+
+ /* Add selected process pid to affinity data */
+ affinity_data->pid = info->pid;
+
+ /* Add CPU count to affinity data */
+ affinity_data->cpu_count = app->config.num_cpus;
+
+ /* Set default toggle signal block states */
+ affinity_data->toggle_single_blocked = FALSE;
+ affinity_data->toggle_all_blocked = FALSE;
+
+ /* Create a label describing the dialog windows intent */
+ label = GTK_WIDGET (procman_make_label_for_mmaps_or_ofiles (
+ _("Select CPUs \"%s\" (PID %u) is allowed to run on:"),
+ info->name.c_str(),
+ info->pid
+ ));
+
+ /* Add label to dialog VBox */
+ gtk_box_pack_start (GTK_BOX (dialog_vbox), label, FALSE, TRUE, 0);
+
+ /* Create scrolled box ("window") */
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+
+ /* Add view class to scrolled box style */
+ scrolled_style = gtk_widget_get_style_context (scrolled);
+ gtk_style_context_add_class (scrolled_style, "view");
+
+ /* Set scrolled box vertical and horizontal policies */
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ /* Set scrolled box shadow */
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_IN);
+
+ /* Create grid for CPU list */
+ cpulist_grid = GTK_GRID (gtk_grid_new ());
+
+ /* Set CPU list grid padding ("border") */
+ gtk_container_set_border_width (GTK_CONTAINER (cpulist_grid), 10);
+
+ /* Set grid row spacing */
+ gtk_grid_set_row_spacing (cpulist_grid, 10);
+
+ /* Create toggle all check box */
+ affinity_data->buttons[0] = gtk_check_button_new_with_label ("Run on all CPUs");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (affinity_data->buttons[0]), TRUE);
+ gtk_widget_set_hexpand (affinity_data->buttons[0], TRUE);
+
+ /* Get process's current affinity */
+ affinity_cpus = glibtop_get_proc_affinity (&affinity, info->pid);
+
+ /* Set toggle all check box based on whether all CPUs are set for this process */
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (affinity_data->buttons[0]),
+ affinity.all
+ );
+
+ /* Add toggle all check box to CPU grid */
+ gtk_grid_attach (cpulist_grid, affinity_data->buttons[0], 0, 0, 1, 1);
+
+ /* Run through all CPU buttons */
+ for (button_n = 1; button_n < app->config.num_cpus + 1; button_n++) {
+ /* Set check box label value to CPU [1..2048] */
+ button_text = g_strdup_printf (_("CPU %d"), button_n);
+
+ /* Create check box button for current CPU */
+ affinity_data->buttons[button_n] = gtk_check_button_new_with_label (button_text);
+ gtk_widget_set_hexpand (affinity_data->buttons[button_n], TRUE);
+
+ /* Add check box to CPU grid */
+ gtk_grid_attach (cpulist_grid, affinity_data->buttons[button_n], 0, button_n, 1, 1);
+
+ /* Connect check box to toggler function */
+ g_signal_connect (affinity_data->buttons[button_n],
+ "toggled",
+ G_CALLBACK (affinity_toggler_single),
+ affinity_data);
+
+ /* Free check box label value gchar */
+ g_free (button_text);
+ }
+
+ /* Run through all CPUs set for this process */
+ for (affinity_i = 0; affinity_i < affinity.number; affinity_i++) {
+ /* Get CPU button index */
+ affinity_cpu = affinity_cpus[affinity_i] + 1;
+
+ /* Set CPU check box active */
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (affinity_data->buttons[affinity_cpu]),
+ TRUE
+ );
+ }
+
+ /* Add CPU grid to scrolled box */
+ gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (cpulist_grid));
+
+ /* Add scrolled box to dialog VBox */
+ gtk_box_pack_start (GTK_BOX (dialog_vbox), scrolled, TRUE, TRUE, 0);
+
+ /* Swap click signal on "Cancel" button */
+ g_signal_connect_swapped (cancel_button,
+ "clicked",
+ G_CALLBACK (gtk_widget_destroy),
+ affinity_data->dialog);
+
+ /* Connect click signal on "Apply" button */
+ g_signal_connect (apply_button,
+ "clicked",
+ G_CALLBACK (set_affinity),
+ affinity_data);
+
+ /* Connect toggle all check box to (de)select all function */
+ g_signal_connect (affinity_data->buttons[0],
+ "toggled",
+ G_CALLBACK (affinity_toggle_all),
+ affinity_data);
+
+ /* Show dialog window */
+ gtk_widget_show_all (affinity_data->dialog);
+}
+
+void
+create_set_affinity_dialog (GsmApplication *app)
+{
+ /* Create a dialog window for each selected process */
+ gtk_tree_selection_selected_foreach (app->selection,
+ create_single_set_affinity_dialog,
+ app);
+}
diff --git a/src/setaffinity.h b/src/setaffinity.h
new file mode 100644
index 0000000..6e1c7c9
--- /dev/null
+++ b/src/setaffinity.h
@@ -0,0 +1,21 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/**
+ * Copyright (C) 2020 Jacob Barkdull
+ *
+ * I, Jacob Barkdull, hereby release this work into the public domain.
+ * This applies worldwide. If this is not legally possible, I grant any
+ * entity the right to use this work for any purpose, without any
+ * conditions, unless such conditions are required by law.
+**/
+
+
+#ifndef _GSM_SETAFFINITY_H_
+#define _GSM_SETAFFINITY_H_
+
+#include <glib.h>
+#include "application.h"
+
+void create_set_affinity_dialog (GsmApplication *app);
+
+#endif /* _GSM_SETAFFINITY_H_ */
diff --git a/src/settings-keys.h b/src/settings-keys.h
new file mode 100644
index 0000000..d1c964e
--- /dev/null
+++ b/src/settings-keys.h
@@ -0,0 +1,45 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_SETTINGS_KEYS_H_
+#define _GSM_SETTINGS_KEYS_H_
+
+#define GSM_GSETTINGS_SCHEMA "org.gnome.gnome-system-monitor"
+#define FONT_SETTINGS_SCHEMA "org.gnome.desktop.interface"
+
+#define FONT_SETTING_SCALING "text-scaling-factor"
+
+#define GSM_SETTINGS_CHILD_OPEN_FILES "openfilestree"
+#define GSM_SETTINGS_CHILD_DISKS "disktreenew"
+#define GSM_SETTINGS_CHILD_PROCESSES "proctree"
+#define GSM_SETTINGS_CHILD_MEMMAP "memmapstree"
+
+#define GSM_SETTING_WINDOW_STATE "window-state"
+#define GSM_SETTING_MAXIMIZED "maximized"
+#define GSM_SETTING_CURRENT_TAB "current-tab"
+#define GSM_SETTING_PROCESS_UPDATE_INTERVAL "update-interval"
+#define GSM_SETTING_SHOW_WHOSE_PROCESSES "show-whose-processes"
+#define GSM_SETTING_SHOW_DEPENDENCIES "show-dependencies"
+#define GSM_SETTING_SHOW_KILL_DIALOG "kill-dialog"
+#define GSM_SETTING_SOLARIS_MODE "solaris-mode"
+#define GSM_SETTING_PROCESS_MEMORY_IN_IEC "process-memory-in-iec"
+#define GSM_SETTING_GRAPH_UPDATE_INTERVAL "graph-update-interval"
+#define GSM_SETTING_CPU_COLORS "cpu-colors"
+#define GSM_SETTING_MEM_COLOR "mem-color"
+#define GSM_SETTING_SWAP_COLOR "swap-color"
+#define GSM_SETTING_NET_IN_COLOR "net-in-color"
+#define GSM_SETTING_NET_OUT_COLOR "net-out-color"
+#define GSM_SETTING_LOGARITHMIC_SCALE "logarithmic-scale"
+#define GSM_SETTING_DRAW_STACKED "cpu-stacked-area-chart"
+#define GSM_SETTING_DRAW_SMOOTH "cpu-smooth-graph"
+#define GSM_SETTING_RESOURCES_MEMORY_IN_IEC "resources-memory-in-iec"
+#define GSM_SETTING_NETWORK_IN_BITS "network-in-bits"
+#define GSM_SETTING_GRAPH_DATA_POINTS "graph-data-points"
+#define GSM_SETTING_NETWORK_TOTAL_UNIT "network-total-unit"
+#define GSM_SETTING_NETWORK_TOTAL_IN_BITS "network-total-in-bits"
+#define GSM_SETTING_SHOW_CPU "show-cpu"
+#define GSM_SETTING_SHOW_MEM "show-mem"
+#define GSM_SETTING_SHOW_NETWORK "show-network"
+#define GSM_SETTING_DISKS_UPDATE_INTERVAL "disks-interval"
+#define GSM_SETTING_SHOW_ALL_FS "show-all-fs"
+#define GSM_SETTING_SMOOTH_REFRESH "smooth-refresh"
+
+#endif /* _GSM_SETTINGS_KEYS_H_ */
diff --git a/src/smooth_refresh.cpp b/src/smooth_refresh.cpp
new file mode 100644
index 0000000..45f28c1
--- /dev/null
+++ b/src/smooth_refresh.cpp
@@ -0,0 +1,151 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <glib.h>
+
+#include <glibtop.h>
+#include <glibtop/proctime.h>
+#include <glibtop/cpu.h>
+
+#include "smooth_refresh.h"
+#include "application.h"
+#include "settings-keys.h"
+#include "util.h"
+
+
+const string SmoothRefresh::KEY(GSM_SETTING_SMOOTH_REFRESH);
+
+
+
+unsigned SmoothRefresh::get_own_cpu_usage()
+{
+ glibtop_cpu cpu;
+ glibtop_proc_time proctime;
+ guint64 elapsed;
+ unsigned usage = PCPU_LO;
+
+ glibtop_get_cpu (&cpu);
+ elapsed = cpu.total - this->last_total_time;
+
+ if (elapsed) { // avoid division by 0
+ glibtop_get_proc_time(&proctime, getpid());
+ usage = (proctime.rtime - this->last_cpu_time) * 100 / elapsed;
+ this->last_cpu_time = proctime.rtime;
+ }
+
+ usage = CLAMP(usage, 0, 100);
+
+ this->last_total_time = cpu.total;
+
+ return usage;
+}
+
+void SmoothRefresh::load_settings_value(Glib::ustring key)
+{
+ this->active = this->settings->get_boolean(key);
+
+ if (this->active)
+ procman_debug("smooth_refresh is enabled");
+}
+
+
+SmoothRefresh::SmoothRefresh(Glib::RefPtr<Gio::Settings> a_settings)
+ :
+ settings(a_settings),
+ active(false),
+ interval(0),
+ last_pcpu(0),
+ last_total_time(0ULL),
+ last_cpu_time(0ULL)
+{
+ this->connection = a_settings->signal_changed("smooth_refresh").connect([this](const Glib::ustring& key) { this->load_settings_value(key); });
+
+ this->reset();
+ this->load_settings_value(KEY);
+}
+
+
+
+void SmoothRefresh::reset()
+{
+ glibtop_cpu cpu;
+ glibtop_proc_time proctime;
+
+ glibtop_get_cpu(&cpu);
+ glibtop_get_proc_time(&proctime, getpid());
+
+ this->interval = GsmApplication::get()->config.update_interval;
+ this->last_pcpu = PCPU_LO;
+ this->last_total_time = cpu.total;
+ this->last_cpu_time = proctime.rtime;
+}
+
+
+
+SmoothRefresh::~SmoothRefresh()
+{
+ if (this->connection)
+ this->connection.disconnect();
+}
+
+
+
+bool
+SmoothRefresh::get(guint &new_interval)
+{
+ const unsigned config_interval = GsmApplication::get()->config.update_interval;
+
+ g_assert(this->interval >= config_interval);
+
+ if (not this->active)
+ return false;
+
+
+ const unsigned pcpu = this->get_own_cpu_usage();
+ /*
+ invariant: MAX_UPDATE_INTERVAL >= interval >= config_interval >= MIN_UPDATE_INTERVAL
+
+ i see 3 cases:
+
+ a) interval is too big (CPU usage < 10%)
+ -> increase interval
+
+ b) interval is too small (CPU usage > 10%) AND interval != config_interval
+ >
+ -> decrease interval
+
+ c) interval is config_interval (start or interval is perfect)
+
+ */
+
+ if (pcpu > PCPU_HI && this->last_pcpu > PCPU_HI)
+ new_interval = this->interval * 11 / 10;
+ else if (this->interval != config_interval && pcpu < PCPU_LO && this->last_pcpu < PCPU_LO)
+ new_interval = this->interval * 9 / 10;
+ else
+ new_interval = this->interval;
+
+ new_interval = CLAMP(new_interval, config_interval, config_interval * 2);
+ new_interval = CLAMP(new_interval, MIN_UPDATE_INTERVAL, MAX_UPDATE_INTERVAL);
+
+ bool changed = this->interval != new_interval;
+
+ if (changed)
+ this->interval = new_interval;
+
+ this->last_pcpu = pcpu;
+
+
+ if (changed) {
+ procman_debug("CPU usage is %3u%%, changed refresh_interval to %u (config %u)",
+ this->last_pcpu,
+ this->interval,
+ config_interval);
+ }
+
+ g_assert(this->interval == new_interval);
+ g_assert(this->interval >= config_interval);
+
+ return changed;
+}
+
diff --git a/src/smooth_refresh.h b/src/smooth_refresh.h
new file mode 100644
index 0000000..8dc1f97
--- /dev/null
+++ b/src/smooth_refresh.h
@@ -0,0 +1,103 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_SMOOTH_REFRESH_H
+#define _GSM_SMOOTH_REFRESH_H
+
+#include <giomm.h>
+#include <glibmm.h>
+#include <string>
+
+#include "util.h"
+
+using std::string;
+
+
+
+class SmoothRefresh
+ : private procman::NonCopyable
+{
+ public:
+
+ /*
+ smooth_refresh_new
+
+ @config_interval : pointer to config_interval so we can observe
+ config_interval changes.
+
+ @return : initialized SmoothRefresh
+ */
+ SmoothRefresh(Glib::RefPtr<Gio::Settings> a_settings);
+
+ ~SmoothRefresh();
+
+ /*
+ smooth_refresh_reset
+
+ Resets state and re-read config_interval
+ */
+ void reset();
+
+ /*
+ smooth_refresh_get
+
+ Computes the new refresh_interval so that CPU usage is lower than
+ SMOOTH_REFRESH_PCPU.
+
+ @new_interval : where the new refresh_interval is stored.
+
+ @return : TRUE is refresh_interval has changed. The new refresh_interval
+ is stored in @new_interval. Else FALSE;
+ */
+ bool get(guint &new_interval);
+
+
+ static const string KEY;
+ static const bool KEY_DEFAULT_VALUE;
+
+ private:
+
+ unsigned get_own_cpu_usage();
+
+ void load_settings_value(Glib::ustring key);
+
+ /*
+ fuzzy logic:
+ - decrease refresh interval only if current CPU% and last CPU%
+ are higher than PCPU_LO
+ - increase refresh interval only if current CPU% and last CPU%
+ are higher than PCPU_HI
+
+ */
+
+ enum
+ {
+ PCPU_HI = 22,
+ PCPU_LO = 18
+ };
+
+ /*
+ -self : procman's PID (so we call getpid() only once)
+
+ -interval : current refresh interval
+
+ -config_interval : pointer to the configuration refresh interval.
+ Used to watch configuration changes
+
+ -interval >= -config_interval
+
+ -last_pcpu : to avoid spikes, the last CPU%. See PCPU_{LO,HI}
+
+ -last_total_time:
+ -last_cpu_time: Save last cpu and process times to compute CPU%
+ */
+
+ Glib::RefPtr<Gio::Settings> settings;
+ bool active;
+ sigc::connection connection;
+ guint interval;
+ unsigned last_pcpu;
+ guint64 last_total_time;
+ guint64 last_cpu_time;
+};
+
+
+#endif /* _GSM_SMOOTH_REFRESH_H */
diff --git a/src/systemd.cpp b/src/systemd.cpp
new file mode 100644
index 0000000..880fc4c
--- /dev/null
+++ b/src/systemd.cpp
@@ -0,0 +1,61 @@
+#include <config.h>
+#include <stdlib.h>
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-login.h>
+#endif
+
+#include "application.h"
+#include "systemd.h"
+
+
+bool
+procman::systemd_logind_running()
+{
+#ifdef HAVE_SYSTEMD
+ static bool init;
+ static bool is_running;
+
+ if (!init) {
+ /* check if logind is running */
+ if (access("/run/systemd/seats/", F_OK) >= 0) {
+ is_running = true;
+ }
+ init = true;
+ }
+
+ return is_running;
+
+#else
+ return false;
+#endif
+}
+
+void
+procman::get_process_systemd_info(ProcInfo *info)
+{
+#ifdef HAVE_SYSTEMD
+ uid_t uid;
+
+ if (!systemd_logind_running())
+ return;
+
+ char* unit = NULL;
+ sd_pid_get_unit(info->pid, &unit);
+ info->unit = make_string(unit);
+
+ char* session = NULL;
+ sd_pid_get_session(info->pid, &session);
+ info->session = make_string(session);
+
+ if (!info->session.empty()) {
+ char* seat = NULL;
+ sd_session_get_seat(info->session.c_str(), &seat);
+ info->seat = make_string(seat);
+ }
+ if (sd_pid_get_owner_uid(info->pid, &uid) >= 0)
+ info->owner = info->lookup_user(uid);
+ else
+ info->owner = "";
+#endif
+}
diff --git a/src/systemd.h b/src/systemd.h
new file mode 100644
index 0000000..6604877
--- /dev/null
+++ b/src/systemd.h
@@ -0,0 +1,12 @@
+#ifndef PROCMAN_SYSTEMD_H_144CA8D6_BF03_11E4_B291_000C298F6617
+#define PROCMAN_SYSTEMD_H_144CA8D6_BF03_11E4_B291_000C298F6617
+
+#include "application.h"
+
+namespace procman
+{
+ bool systemd_logind_running();
+ void get_process_systemd_info(ProcInfo *info);
+}
+
+#endif
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..59cb7b3
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,766 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#include <config.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <cstdio>
+#include <random>
+#include <tuple>
+#include <vector>
+
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <glibtop/proctime.h>
+#include <glibtop/procstate.h>
+
+#include "util.h"
+#include "application.h"
+
+extern "C" {
+#include "legacy/e_date.h"
+}
+
+
+const char*
+format_process_state(guint state)
+{
+ const char *status;
+
+ switch (state)
+ {
+ case GLIBTOP_PROCESS_RUNNING:
+ status = _("Running");
+ break;
+
+ case GLIBTOP_PROCESS_STOPPED:
+ status = _("Stopped");
+ break;
+
+ case GLIBTOP_PROCESS_ZOMBIE:
+ status = _("Zombie");
+ break;
+
+ case GLIBTOP_PROCESS_UNINTERRUPTIBLE:
+ status = _("Uninterruptible");
+ break;
+
+ default:
+ status = _("Sleeping");
+ break;
+ }
+
+ return status;
+}
+
+
+
+static char *
+mnemonic_safe_process_name(const char *process_name)
+{
+ const char *p;
+ GString *name;
+
+ name = g_string_new ("");
+
+ for(p = process_name; *p; ++p)
+ {
+ g_string_append_c (name, *p);
+
+ if(*p == '_')
+ g_string_append_c (name, '_');
+ }
+
+ return g_string_free (name, FALSE);
+}
+
+
+
+static inline unsigned divide(unsigned *q, unsigned *r, unsigned d)
+{
+ *q = *r / d;
+ *r = *r % d;
+ return *q != 0;
+}
+
+
+/*
+ * @param d: duration in centiseconds
+ * @type d: unsigned
+ */
+char *
+procman::format_duration_for_display(unsigned centiseconds)
+{
+ unsigned weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0;
+
+ (void)(divide(&seconds, &centiseconds, 100)
+ && divide(&minutes, &seconds, 60)
+ && divide(&hours, &minutes, 60)
+ && divide(&days, &hours, 24)
+ && divide(&weeks, &days, 7));
+
+ if (weeks)
+ /* xgettext: weeks, days */
+ return g_strdup_printf(_("%uw%ud"), weeks, days);
+
+ if (days)
+ /* xgettext: days, hours (0 -> 23) */
+ return g_strdup_printf(_("%ud%02uh"), days, hours);
+
+ if (hours)
+ /* xgettext: hours (0 -> 23), minutes, seconds */
+ return g_strdup_printf(_("%u:%02u:%02u"), hours, minutes, seconds);
+
+ /* xgettext: minutes, seconds, centiseconds */
+ return g_strdup_printf(_("%u:%02u.%02u"), minutes, seconds, centiseconds);
+}
+
+
+
+GtkLabel*
+procman_make_label_for_mmaps_or_ofiles(const char *format,
+ const char *process_name,
+ unsigned pid)
+{
+ GtkLabel* label;
+ char *name, *title;
+
+ name = mnemonic_safe_process_name (process_name);
+ title = g_strdup_printf(format, name, pid);
+ label = GTK_LABEL (gtk_label_new_with_mnemonic (title));
+ gtk_widget_set_valign (GTK_WIDGET (label), GTK_ALIGN_CENTER);
+ gtk_widget_set_halign (GTK_WIDGET (label), GTK_ALIGN_START);
+
+ g_free (title);
+ g_free (name);
+
+ return label;
+}
+
+
+
+/**
+ * procman::format_size:
+ * @size:
+ *
+ * Formats the file size passed in @bytes in a way that is easy for
+ * the user to read. Gives the size in bytes, kibibytes, mebibytes or
+ * gibibytes, choosing whatever is appropriate.
+ *
+ * Returns: a newly allocated string with the size ready to be shown.
+ **/
+
+gchar*
+procman::format_size(guint64 size, bool want_bits)
+{
+ if (want_bits)
+ size *= 8;
+
+ const GFormatSizeFlags flags = (want_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_IEC_UNITS);
+ return g_format_size_full(size, flags);
+}
+
+gchar *
+procman::get_nice_level (gint nice)
+{
+ if (nice < -7)
+ return _("Very High");
+ else if (nice < -2)
+ return _("High");
+ else if (nice < 3)
+ return _("Normal");
+ else if (nice < 7)
+ return _("Low");
+ else
+ return _("Very Low");
+}
+
+gchar *
+procman::get_nice_level_with_priority (gint nice)
+{
+ if (nice < -7)
+ return _("Very High Priority");
+ else if (nice < -2)
+ return _("High Priority");
+ else if (nice < 3)
+ return _("Normal Priority");
+ else if (nice < 7)
+ return _("Low Priority");
+ else
+ return _("Very Low Priority");
+}
+
+gchar*
+format_byte_size(guint64 size, bool want_iec_format)
+{
+ const GFormatSizeFlags flags = (want_iec_format ? G_FORMAT_SIZE_IEC_UNITS : G_FORMAT_SIZE_DEFAULT);
+ return g_format_size_full(size, flags);
+}
+
+gboolean
+load_symbols(const char *module, ...)
+{
+ GModule *mod;
+ gboolean found_all = TRUE;
+ va_list args;
+
+ mod = g_module_open(module, static_cast<GModuleFlags>(G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL));
+
+ if (!mod)
+ return FALSE;
+
+ procman_debug("Found %s", module);
+
+ va_start(args, module);
+
+ while (1) {
+ const char *name;
+ void **symbol;
+
+ name = va_arg(args, char*);
+
+ if (!name)
+ break;
+
+ symbol = va_arg(args, void**);
+
+ if (g_module_symbol(mod, name, symbol)) {
+ procman_debug("Loaded %s from %s", name, module);
+ }
+ else {
+ procman_debug("Could not load %s from %s", name, module);
+ found_all = FALSE;
+ break;
+ }
+ }
+
+ va_end(args);
+
+
+ if (found_all)
+ g_module_make_resident(mod);
+ else
+ g_module_close(mod);
+
+ return found_all;
+}
+
+
+static gboolean
+is_debug_enabled(void)
+{
+ static gboolean init;
+ static gboolean enabled;
+
+ if (!init) {
+ enabled = g_getenv("GNOME_SYSTEM_MONITOR_DEBUG") != NULL;
+ init = TRUE;
+ }
+
+ return enabled;
+}
+
+
+static double
+get_relative_time(void)
+{
+ static unsigned long start_time;
+
+ if (G_UNLIKELY(!start_time)) {
+ glibtop_proc_time buf;
+ glibtop_get_proc_time(&buf, getpid());
+ start_time = buf.start_time;
+ }
+
+ return 1e-6 * g_get_monotonic_time () - start_time;
+}
+
+static gdouble
+get_size_from_column(GtkTreeModel* model, GtkTreeIter* first,
+ const guint index)
+{
+ GValue value = { 0 };
+ gtk_tree_model_get_value(model, first, index, &value);
+
+ gdouble size;
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_UINT:
+ size = g_value_get_uint(&value);
+ break;
+ case G_TYPE_ULONG:
+ size = g_value_get_ulong(&value);
+ break;
+ case G_TYPE_UINT64:
+ size = g_value_get_uint64(&value);
+ break;
+ case G_TYPE_DOUBLE:
+ size = g_value_get_double(&value);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+ return size;
+}
+
+void
+procman_debug_real(const char *file, int line, const char *func,
+ const char *format, ...)
+{
+ va_list args;
+ char *msg;
+
+ if (G_LIKELY(!is_debug_enabled()))
+ return;
+
+ va_start(args, format);
+ msg = g_strdup_vprintf(format, args);
+ va_end(args);
+
+ g_print ("[%.3f %s:%d %s] %s\n", get_relative_time(), file, line, func, msg);
+
+ g_free(msg);
+}
+
+
+// h is in [0.0; 1.0] (and not [0°; 360°] )
+// s is in [0.0; 1.0]
+// v is in [0.0; 1.0]
+// https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
+std::tuple<double, double, double> hsv_to_rgb(double h, double s, double v)
+{
+ const double c = v * s;
+ const double hp = h * 6; // 360° / 60°
+ const double x = c * (1 - std::abs(std::fmod(hp, 2.0) - 1));
+
+ double r1 = 0 , g1 = 0, b1 = 0;
+
+ switch (int(hp)) {
+ case 0: r1 = c; g1 = x; b1 = 0; break;
+ case 1: r1 = x; g1 = c; b1 = 0; break;
+ case 2: r1 = 0; g1 = c; b1 = x; break;
+ case 3: r1 = 0; g1 = x; b1 = c; break;
+ case 4: r1 = x; g1 = 0; b1 = c; break;
+ case 5: r1 = c; g1 = 0; b1 = x; break;
+ }
+
+ const double m = v - c;
+
+ return {r1 + m, g1 + m, b1 + m};
+}
+
+
+std::string rgb_to_color_string(const std::tuple<double, double, double> &t)
+{
+ char b[14];
+ auto c = [](double d) { return (unsigned short)(0xffff * d); };
+ std::snprintf(b, sizeof b, "#%04x%04x%04x", c(std::get<0>(t)), c(std::get<1>(t)), c(std::get<2>(t)));
+ return {b, 13};
+}
+
+
+namespace procman
+{
+
+ // http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
+ std::vector<std::string> generate_colors(unsigned n)
+ {
+ constexpr double golden_ration_conjugate = 0.618033988749894848204586834;
+
+ std::vector<std::string> values;
+
+ std::random_device re;
+ std::uniform_real_distribution<> dist(0.0, 1.0);
+
+ double h = dist(re);
+
+ for (unsigned i = 0; i < n; i++) {
+ h = CLAMP(h, 0.0, 1.0);
+ auto rgb = hsv_to_rgb(h, 0.5, 0.95);
+ values.push_back(rgb_to_color_string(rgb));
+ h = std::fmod(h + golden_ration_conjugate, 1.0);
+ }
+
+ return values;
+ }
+
+ void size_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ guint64 size;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_ULONG:
+ size = g_value_get_ulong(&value);
+ break;
+
+ case G_TYPE_UINT64:
+ size = g_value_get_uint64(&value);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+
+ char *str = g_format_size_full(size, G_FORMAT_SIZE_IEC_UNITS);
+ g_object_set(renderer, "text", str, NULL);
+ g_free(str);
+ }
+
+ void percentage_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ gdouble size;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+ size = g_value_get_double(&value);
+ g_value_unset(&value);
+
+ char *str = g_strdup_printf("%.2f", size);
+ g_object_set(renderer, "text", str, NULL);
+ g_free(str);
+ }
+
+
+ /*
+ Same as above but handles size == 0 as not available
+ */
+ void size_na_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ guint64 size;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_ULONG:
+ size = g_value_get_ulong(&value);
+ break;
+
+ case G_TYPE_UINT64:
+ size = g_value_get_uint64(&value);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+
+ if (size == 0) {
+ g_object_set (renderer,
+ "text", _("N/A"),
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+ }
+ else {
+ char *str = format_byte_size(size, GsmApplication::get()->config.process_memory_in_iec);
+ g_object_set (renderer,
+ "text", str,
+ "style", PANGO_STYLE_NORMAL,
+ NULL);
+ g_free(str);
+ }
+
+ }
+
+ void io_rate_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ guint64 size;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_ULONG:
+ size = g_value_get_ulong(&value);
+ break;
+
+ case G_TYPE_UINT64:
+ size = g_value_get_uint64(&value);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+
+ if (size == 0) {
+ g_object_set (renderer,
+ "text", _("N/A"),
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+ }
+ else {
+ g_object_set (renderer,
+ "text", procman::format_rate(size, FALSE).c_str(),
+ "style", PANGO_STYLE_NORMAL,
+ NULL);
+ }
+
+ }
+
+ /*
+ Cell data function to format a size value with SI units (to be used only for disk size, see bugzilla 693630)
+ */
+ void size_si_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ guint64 size;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_ULONG:
+ size = g_value_get_ulong(&value);
+ break;
+
+ case G_TYPE_UINT64:
+ size = g_value_get_uint64(&value);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+
+ char *str = g_format_size(size);
+ g_object_set(renderer, "text", str, NULL);
+ g_free(str);
+ }
+
+ void duration_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ unsigned time;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_ULONG:
+ time = g_value_get_ulong(&value);
+ break;
+
+ case G_TYPE_UINT64:
+ time = g_value_get_uint64(&value);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+
+ time = 100 * time / GsmApplication::get()->frequency;
+ char *str = format_duration_for_display(time);
+ g_object_set(renderer, "text", str, NULL);
+ g_free(str);
+ }
+
+
+ void time_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ time_t time;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_ULONG:
+ time = g_value_get_ulong(&value);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+
+ char *str = procman_format_date_for_display(time);
+ g_object_set(renderer, "text", str, NULL);
+ g_free(str);
+ }
+
+ void status_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ guint state;
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_UINT:
+ state = g_value_get_uint(&value);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_value_unset(&value);
+
+ const char *str = format_process_state(state);
+ g_object_set(renderer, "text", str, NULL);
+ }
+
+ void priority_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ GValue value = { 0 };
+
+ gtk_tree_model_get_value(model, iter, index, &value);
+
+ gint priority = g_value_get_int(&value);
+
+ g_value_unset(&value);
+
+ g_object_set(renderer, "text", procman::get_nice_level(priority), NULL);
+
+ }
+
+ gint priority_compare_func(GtkTreeModel* model, GtkTreeIter* first,
+ GtkTreeIter* second, gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+ GValue value1 = { 0 };
+ GValue value2 = { 0 };
+ gtk_tree_model_get_value(model, first, index, &value1);
+ gtk_tree_model_get_value(model, second, index, &value2);
+ gint result = g_value_get_int(&value1) - g_value_get_int(&value2);
+ g_value_unset(&value1);
+ g_value_unset(&value2);
+ return result;
+ }
+
+ gint number_compare_func(GtkTreeModel* model, GtkTreeIter* first,
+ GtkTreeIter* second, gpointer user_data)
+ {
+ const guint index = GPOINTER_TO_UINT(user_data);
+
+ gdouble size1, size2;
+ size1 = get_size_from_column(model, first, index);
+ size2 = get_size_from_column(model, second, index);
+
+ if ( size2 > size1 )
+ return 1;
+ else if ( size2 < size1 )
+ return -1;
+ return 0;
+ }
+
+ template<>
+ void tree_store_update<const char>(GtkTreeModel* model, GtkTreeIter* iter, int column, const char* new_value)
+ {
+ char* current_value;
+
+ gtk_tree_model_get(model, iter, column, &current_value, -1);
+
+ if (g_strcmp0(current_value, new_value) != 0)
+ gtk_tree_store_set(GTK_TREE_STORE(model), iter, column, new_value, -1);
+
+ g_free(current_value);
+ }
+
+
+ std::string format_rate(guint64 rate, bool want_bits)
+ {
+ char* bytes = procman::format_size(rate, want_bits);
+ // xgettext: rate, 10MiB/s or 10Mbit/s
+ std::string formatted_rate(make_string(g_strdup_printf(_("%s/s"), bytes)));
+ g_free(bytes);
+ return formatted_rate;
+ }
+
+
+ std::string format_network(guint64 rate)
+ {
+ char* bytes = procman::format_size(rate, GsmApplication::get()->config.network_total_in_bits);
+ std::string formatted(bytes);
+ g_free(bytes);
+ return formatted;
+ }
+
+
+ std::string format_network_rate(guint64 rate)
+ {
+ return procman::format_rate(rate, GsmApplication::get()->config.network_in_bits);
+ }
+
+}
+
+Glib::ustring
+get_monospace_system_font_name ()
+{
+ return Gio::Settings::create ("org.gnome.desktop.interface")->get_string ("monospace-font-name");
+}
+
+
+GtkLabel *
+make_tnum_label (void)
+{
+ PangoAttrList *attrs = make_tnum_attr_list ();
+ GtkWidget *label = gtk_label_new (NULL);
+
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+
+ g_clear_pointer (&attrs, pango_attr_list_unref);
+
+ return GTK_LABEL (label);
+}
+
+
+PangoAttrList *
+make_tnum_attr_list (void)
+{
+ PangoAttrList *attrs = NULL;
+
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_font_features_new ("tnum=1"));
+
+ return attrs;
+}
+
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..750fa38
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,177 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+#ifndef _GSM_UTIL_H_
+#define _GSM_UTIL_H_
+
+#include <gtkmm.h>
+#include <string>
+#include <vector>
+
+using std::string;
+
+GtkLabel*
+procman_make_label_for_mmaps_or_ofiles(const char *format,
+ const char *process_name,
+ unsigned pid);
+
+gboolean
+load_symbols(const char *module, ...) G_GNUC_NULL_TERMINATED;
+
+const char*
+format_process_state(guint state);
+
+gchar*
+format_byte_size(guint64 size, bool want_iec_format);
+
+void
+procman_debug_real(const char *file, int line, const char *func,
+ const char *format, ...) G_GNUC_PRINTF(4, 5);
+
+#define procman_debug(FMT, ...) procman_debug_real(__FILE__, __LINE__, __func__, FMT, ##__VA_ARGS__)
+
+Glib::ustring get_monospace_system_font_name (void);
+GtkLabel *make_tnum_label (void);
+PangoAttrList *make_tnum_attr_list (void);
+std::tuple<double, double, double> hsv_to_rgb(double h, double s, double v);
+std::string rgb_to_color_string(const std::tuple<double, double, double> &t);
+
+inline string make_string(char *c_str)
+{
+ if (!c_str) {
+ procman_debug("NULL string");
+ return string();
+ }
+
+ string s(c_str);
+ g_free(c_str);
+ return s;
+}
+
+
+namespace procman
+{
+ // create a list of n color strings
+ std::vector<std::string> generate_colors(unsigned n);
+
+ char* format_duration_for_display(unsigned centiseconds);
+
+ void size_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+
+ void io_rate_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+
+ void size_na_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+
+ void size_si_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+
+
+ void duration_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+
+ void time_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+ void percentage_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+
+ void status_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+ void priority_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+ GtkTreeModel *model, GtkTreeIter *iter,
+ gpointer user_data);
+ gint priority_compare_func(GtkTreeModel* model, GtkTreeIter* first,
+ GtkTreeIter* second, gpointer user_data);
+ gint number_compare_func(GtkTreeModel* model, GtkTreeIter* first,
+ GtkTreeIter* second, gpointer user_data);
+
+
+ template<typename T>
+ void poison(T &t, char c)
+ {
+ memset(&t, c, sizeof t);
+ }
+
+
+
+ //
+ // Stuff to update a tree_store in a smart way
+ //
+
+ template<typename T>
+ void tree_store_update(GtkTreeModel* model, GtkTreeIter* iter, int column, const T& new_value)
+ {
+ T current_value;
+
+ gtk_tree_model_get(model, iter, column, &current_value, -1);
+
+ if (current_value != new_value)
+ gtk_tree_store_set(GTK_TREE_STORE(model), iter, column, new_value, -1);
+ }
+
+ // undefined
+ // catch every thing about pointers
+ // just to make sure i'm not doing anything wrong
+ template<typename T>
+ void tree_store_update(GtkTreeModel* model, GtkTreeIter* iter, int column, T* new_value);
+
+ // specialized versions for strings
+ template<>
+ void tree_store_update<const char>(GtkTreeModel* model, GtkTreeIter* iter, int column, const char* new_value);
+
+ template<>
+ inline void tree_store_update<char>(GtkTreeModel* model, GtkTreeIter* iter, int column, char* new_value)
+ {
+ tree_store_update<const char>(model, iter, column, new_value);
+ }
+
+ gchar* format_size(guint64 size, bool want_bits = false);
+
+ gchar* get_nice_level (gint nice);
+
+ gchar* get_nice_level_with_priority (gint nice);
+
+ std::string format_rate(guint64 rate, bool want_bits = false);
+
+ std::string format_network(guint64 rate);
+ std::string format_network_rate(guint64 rate);
+
+ class NonCopyable
+ {
+ protected:
+ NonCopyable() {} // = default
+ ~NonCopyable() {} // = default
+ private:
+ NonCopyable(const NonCopyable&) /* = delete */;
+ NonCopyable& operator=(const NonCopyable&) /* = delete */;
+ };
+
+
+ // join the elements of c with sep
+ template<typename C, typename S>
+ auto join(const C& c, const S& sep) -> decltype(c[0] + sep)
+ {
+ decltype(c[0] + sep) r;
+ bool first = true;
+
+ for(const auto& e : c) {
+ if (!first) {
+ r += sep;
+ }
+ first = false;
+ r += e;
+ }
+
+ return r;
+ }
+}
+
+#endif /* _GSM_UTIL_H_ */