diff options
Diffstat (limited to 'src')
57 files changed, 11321 insertions, 0 deletions
diff --git a/src/application.cpp b/src/application.cpp new file mode 100644 index 0000000..7dff723 --- /dev/null +++ b/src/application.cpp @@ -0,0 +1,430 @@ +/* -*- 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 <signal.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_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(); + load_graph_reset(app->cpu_graph); +} + +static void +cb_network_in_bits_changed (Gio::Settings& settings, Glib::ustring key, GsmApplication* app) +{ + app->config.network_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 +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); + + 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 ("#f25915e815e8"); + 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.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.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); + }); + + 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); + + 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); + procman::OptionGroup option_group; + context.set_main_group(option_group); + + 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() +{ + Gtk::Application::on_startup(); + + load_resources (); + + 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("<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..49efc80 --- /dev/null +++ b/src/application.h @@ -0,0 +1,245 @@ +/* -*- 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 <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), + mem_color(), + swap_color(), + net_in_color(), + net_out_color(), + bg_color(), + frame_color(), + num_cpus(0), + solaris_mode(false), + draw_stacked(false), + draw_smooth(true), + network_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; + 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 draw_stacked; + bool draw_smooth; + bool network_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(0U), + 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; + guint 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; + GtkApplicationWindow *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..fe6c7e1 --- /dev/null +++ b/src/disks.cpp @@ -0,0 +1,476 @@ +/* -*- 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; + 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: + g_object_set(cell, "xalign", 1.0f, NULL); + gtk_tree_view_column_set_cell_data_func(col, cell, + &procman::size_si_cell_data_func, + GUINT_TO_POINTER(i), + NULL); + 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(); + g_object_set(cell, "xalign", 1.0f, NULL); + 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..d6b4a8d --- /dev/null +++ b/src/gsm.gresource.xml @@ -0,0 +1,13 @@ +<?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> + </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..fac941c --- /dev/null +++ b/src/interface.cpp @@ -0,0 +1,849 @@ +/* -*- 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 <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 "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 +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); + + 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; + 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_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)); + 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 = GTK_LABEL (gtk_label_new (NULL)); + + /* Reserve some space to avoid the layout changing with the values. */ + gtk_label_set_width_chars(cpu_label, 6); + 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_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_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", + 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_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_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) +{ + GtkApplicationWindow *main_window; + GtkStack *stack; + GMenuModel *window_menu_model; + GMenuModel *process_menu_model; + GdkDisplay *display; + GdkMonitor *monitor; + GdkRectangle monitor_geometry; + const char* session; + + 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); + + main_window = GTK_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; + + session = g_getenv ("XDG_CURRENT_DESKTOP"); + if (session && !strstr (session, "GNOME")){ + GtkBox *mainbox; + GtkHeaderBar *headerbar; + + mainbox = GTK_BOX (gtk_builder_get_object (builder, "main_box")); + headerbar = GTK_HEADER_BAR (gtk_builder_get_object (builder, "header_bar")); + gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (headerbar)), "titlebar"); + gtk_window_set_titlebar (GTK_WINDOW (main_window), NULL); + gtk_header_bar_set_show_close_button (headerbar, FALSE); + gtk_box_pack_start (mainbox, GTK_WIDGET (headerbar), FALSE, FALSE, 0); + } + + 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 }, + { "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 }, + { "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", + "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..e23e9b6 --- /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_STATE_FLAG_INSENSITIVE, 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)); + 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..9d874b1 --- /dev/null +++ b/src/load-graph.cpp @@ -0,0 +1,959 @@ +/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +#include <config.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" + + +void LoadGraph::clear_background() +{ + if (background) { + cairo_surface_destroy (background); + background = NULL; + } +} + + +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; + default: + n = 5; + } + + return n; +} + + + +const int FRAME_WIDTH = 4; +static void draw_background(LoadGraph *graph) { + GtkAllocation allocation; + cairo_t *cr; + guint i; + unsigned num_bars; + char *caption; + PangoLayout* layout; + 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) / (LoadGraph::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_STATE_FLAG_NORMAL, &fg); + + cairo_paint_with_alpha (cr, 0.0); + layout = pango_cairo_create_layout (cr); + gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL, 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_STATE_FLAG_NORMAL, &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); + if (graph->type == LOAD_GRAPH_NET) { + // operation orders matters so it's 0 if i == num_bars + guint64 rate = graph->net.max - (i * graph->net.max / num_bars); + const std::string captionstr(procman::format_network_rate(rate)); + caption = g_strdup(captionstr.c_str()); + } else { + // operation orders matters so it's 0 if i == num_bars + caption = g_strdup_printf("%d %%", 100 - i * (100 / num_bars)); + } + pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT); + pango_layout_set_text (layout, caption, -1); + pango_layout_get_extents (layout, NULL, &extents); + cairo_move_to (cr, graph->draw_width - graph->indent - 23, + y - 1.0 * extents.height / PANGO_SCALE / 2); + 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 * (LoadGraph::NUM_POINTS - 2) / 1000; + + 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); + unsigned seconds = total_seconds - i * total_seconds / 6; + const char* format; + if (i == 0) + format = dngettext(GETTEXT_PACKAGE, "%u second", "%u seconds", seconds); + else + format = "%u"; + caption = g_strdup_printf(format, seconds); + pango_layout_set_text (layout, caption, -1); + pango_layout_get_extents (layout, NULL, &extents); + cairo_move_to (cr, + (ceil(x) + 0.5 + graph->indent) - (1.0 * extents.width / PANGO_SCALE / 2), + 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 gboolean +load_graph_configure (GtkWidget *widget, + GdkEventConfigure *event, + gpointer data_ptr) +{ + GtkAllocation allocation; + LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr); + + 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); + 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)LoadGraph::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 = graph->type != LOAD_GRAPH_CPU || 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.5f); + // then draw the path of the line. + // Loop starts at 1 because the curve accesses the 0th data point. + for (i = 1; i < LoadGraph::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.5, + x_offset - ((i - 0.5f) * graph->graph_delx), + (1.0 - graph->data[i][j]) * graph->real_draw_height + 3.5, + x_offset - (i * graph->graph_delx), + (1.0 - graph->data[i][j]) * graph->real_draw_height + 3.5); + } else { + cairo_line_to (cr, x_offset - (i * graph->graph_delx), + (1.0 - graph->data[i][j]) * graph->real_draw_height + 3.5); + } + + } + if (drawStacked) { + // Draw the remaining outline of the area: + // Left bottom corner + cairo_rel_line_to (cr, 0, graph->real_draw_height + 3.5); + // 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 */ + 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 = g_format_size_full(used, G_FORMAT_SIZE_IEC_UNITS); + cached_text = g_format_size_full(cached, G_FORMAT_SIZE_IEC_UNITS); + total_text = g_format_size_full(total, G_FORMAT_SIZE_IEC_UNITS); + if (total == 0) { + text = g_strdup(_("not available")); + } else { + // xgettext: 540MiB (53 %) of 1.0 GiB + 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" + 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] = mempercent; + graph->data[0][1] = swap.total>0 ? 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); + graph->net.values[graph->net.cur] = dmax; + graph->net.cur = (graph->net.cur + 1) % LoadGraph::NUM_POINTS; + + 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[LoadGraph::NUM_POINTS]); + + // + // 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 < LoadGraph::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; + gboolean first = true; + 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 suddent 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; + } + + first = first && (graph->net.time==0); + graph->net.last_in = in; + graph->net.last_out = out; + graph->net.time = time; + + if (!first) + 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[0], + &graph->data[LoadGraph::NUM_POINTS - 1], + &graph->data[LoadGraph::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(7 * fontsize), + indent(24.0), + n(0), + type(type), + speed(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(), + main_widget(NULL), + disp(NULL), + background(NULL), + timer_index(0), + draw(FALSE), + labels(), + mem_color_picker(NULL), + swap_color_picker(NULL), + cpu(), + net() +{ + LoadGraph * const graph = 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: + memset(&cpu, 0, sizeof cpu); + n = GsmApplication::get()->config.num_cpus; + + for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i) + labels.cpu[i] = GTK_LABEL (gtk_label_new(NULL)); + + break; + + case LOAD_GRAPH_MEM: + n = 2; + labels.memory = GTK_LABEL (gtk_label_new(NULL)); + 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 = GTK_LABEL (gtk_label_new(NULL)); + 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: + memset(&net, 0, sizeof net); + n = 2; + net.max = 1; + labels.net_in = GTK_LABEL (gtk_label_new(NULL)); + 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 = GTK_LABEL (gtk_label_new(NULL)); + 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 = GTK_LABEL (gtk_label_new(NULL)); + 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 = GTK_LABEL (gtk_label_new(NULL)); + 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; + + 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: + 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); + + + /* Allocate data in a contiguous block */ + data_block = std::vector<double>(n * LoadGraph::NUM_POINTS, -1.0); + + for (guint i = 0; i < LoadGraph::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 / graph->frames_per_unit, + 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 / graph->frames_per_unit, + load_graph_update, + graph); + } + + 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..1be59ad --- /dev/null +++ b/src/load-graph.h @@ -0,0 +1,135 @@ +/* -*- 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" + +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 NUM_POINTS = 60 + 2; + static const unsigned GRAPH_MIN_HEIGHT = 40; + + LoadGraph(guint type); + ~LoadGraph(); + + unsigned num_bars() const; + void clear_background(); + + double fontsize; + double rmargin; + /* left margin */ + double indent; + + guint n; + gint type; + guint speed; + 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; + double* data[NUM_POINTS]; + + GtkBox *main_widget; + GtkDrawingArea *disp; + + cairo_surface_t *background; + + guint timer_index; + + gboolean draw; + + LoadGraphLabels labels; + GsmColorButton *mem_color_picker; + GsmColorButton *swap_color_picker; + + /* union { */ + struct + { + 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 + { + guint64 last_in, last_out; + guint64 time; + guint64 max; + unsigned values[NUM_POINTS]; + size_t cur; + } 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); + +/* 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..02053c7 --- /dev/null +++ b/src/memmaps.cpp @@ -0,0 +1,486 @@ +/* -*- 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; + + 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); + + 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..76c5320 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,106 @@ +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', + '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', + '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, + libgtop, + 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..1dcd152 --- /dev/null +++ b/src/org.gnome.gnome-system-monitor.gschema.xml.in @@ -0,0 +1,733 @@ +<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="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="250" max="100000"/> + <default>1000</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="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="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 chart as smooth graph using Bezier curves + </summary> + <description>If TRUE, system-monitor shows the CPU chart as a smoothed graph, otherwise as a line chart. + </description> + </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..9f18fd7 --- /dev/null +++ b/src/prefsdialog.cpp @@ -0,0 +1,308 @@ +/* -*- 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 GtkDialog *prefs_dialog = NULL; + +static void +prefs_dialog_button_pressed (GtkDialog *dialog, gint id, gpointer data) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); + prefs_dialog = NULL; +} + + +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 = int(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; +}; + +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"); + + GtkNotebook *notebook; + GtkAdjustment *adjustment; + GtkSpinButton *spin_button; + GtkCheckButton *check_button; + GtkCheckButton *smooth_button; + 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 = GTK_DIALOG (gtk_builder_get_object (builder, "preferences_dialog")); + + notebook = GTK_NOTEBOOK (gtk_builder_get_object (builder, "notebook")); + + 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_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "smooth_button")); + g_settings_bind(app->settings->gobj (), SmoothRefresh::KEY.c_str(), smooth_button, "active", G_SETTINGS_BIND_DEFAULT); + + check_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "check_button")); + g_settings_bind (app->settings->gobj (), GSM_SETTING_SHOW_KILL_DIALOG, + check_button, "active", + G_SETTINGS_BIND_DEFAULT); + + GtkCheckButton *solaris_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "solaris_button")); + g_settings_bind (app->settings->gobj (), GSM_SETTING_SOLARIS_MODE, + solaris_button, "active", + G_SETTINGS_BIND_DEFAULT); + + GtkCheckButton *draw_stacked_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "draw_stacked_button")); + g_settings_bind (app->settings->gobj (), GSM_SETTING_DRAW_STACKED, + draw_stacked_button, "active", + G_SETTINGS_BIND_DEFAULT); + + GtkCheckButton *draw_smooth_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "draw_smooth_button")); + g_settings_bind (app->settings->gobj (), GSM_SETTING_DRAW_SMOOTH, + draw_smooth_button, "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.25, + 100.0, 0.25, 1.0, 0); + g_signal_connect (G_OBJECT (spin_button), "focus_out_event", + G_CALLBACK(SBU::callback), + &graph_interval_updater); + + GtkCheckButton *bits_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "bits_button")); + g_settings_bind(app->settings->gobj (), GSM_SETTING_NETWORK_IN_BITS, + bits_button, "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_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "all_devices_check")); + g_settings_bind (app->settings->gobj (), GSM_SETTING_SHOW_ALL_FS, + check_button, "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_widget_show_all (GTK_WIDGET (prefs_dialog)); + g_signal_connect (G_OBJECT (prefs_dialog), "response", + G_CALLBACK (prefs_dialog_button_pressed), app); + + auto current_tab = app->settings->get_string(GSM_SETTING_CURRENT_TAB); + if (current_tab == "processes") + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 0); + else if (current_tab == "resources") + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 1); + else if (current_tab == "disks") + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 2); + + 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..5e3f833 --- /dev/null +++ b/src/prefsdialog.h @@ -0,0 +1,9 @@ +/* -*- 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); + +#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..1154e0c --- /dev/null +++ b/src/procdialogs.cpp @@ -0,0 +1,315 @@ +/* -*- 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); + + 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; + + 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(); + } +} + + +/* + * 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..e94a7e5 --- /dev/null +++ b/src/procdialogs.h @@ -0,0 +1,52 @@ +/* -*- 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 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..5e751a5 --- /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("%d%%", 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..3f0026e --- /dev/null +++ b/src/proctable.cpp @@ -0,0 +1,1239 @@ +/* -*- 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_UINT, /* % 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); + + for (i = COL_USER; i <= COL_PRIORITY; i++) { + GtkTreeViewColumn *col; + GtkCellRenderer *cell; + +#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_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; + } + + // 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; + } + } + 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(), ¤t) != 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, ¤t.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, ¤t.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, ¤t.node)); + + orphans.push_back(¤t); + gtk_tree_store_remove(GTK_TREE_STORE(model), ¤t.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 = 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, ¤t_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/settings-keys.h b/src/settings-keys.h new file mode 100644 index 0000000..87fbb9f --- /dev/null +++ b/src/settings-keys.h @@ -0,0 +1,36 @@ +/* -*- 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 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_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_DRAW_STACKED "cpu-stacked-area-chart" +#define GSM_SETTING_DRAW_SMOOTH "cpu-smooth-graph" +#define GSM_SETTING_NETWORK_IN_BITS "network-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..044fc92 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,637 @@ +/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +#include <config.h> + +#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, ¢iseconds, 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"); +} + +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 guint64 +get_size_from_column(GtkTreeModel* model, GtkTreeIter* first, + const guint index) +{ + GValue value = { 0 }; + gtk_tree_model_get_value(model, first, index, &value); + + guint64 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; + 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); +} + + + +namespace procman +{ + 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); + } + + + /* + 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) { + char *str = g_strdup_printf ("<i>%s</i>", _("N/A")); + g_object_set(renderer, "markup", str, NULL); + g_free(str); + } + else { + char *str = g_format_size_full(size, G_FORMAT_SIZE_IEC_UNITS); + g_object_set(renderer, "text", str, 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) { + char *str = g_strdup_printf ("<i>%s</i>", _("N/A")); + g_object_set(renderer, "markup", str, NULL); + g_free(str); + } + else { + g_object_set(renderer, "text", procman::format_rate(size, FALSE).c_str(), 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); + + guint64 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, ¤t_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_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"); +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..d76244e --- /dev/null +++ b/src/util.h @@ -0,0 +1,163 @@ +/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +#ifndef _GSM_UTIL_H_ +#define _GSM_UTIL_H_ + +#include <gtkmm.h> +#include <string> + +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); + +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); + +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 +{ + 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 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, ¤t_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_ */ |