diff options
author | Perberos <[email protected]> | 2011-11-08 16:22:08 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-11-08 16:22:08 -0300 |
commit | f45852ab2a7126f354079499fc8b03976c0eab27 (patch) | |
tree | 885c32d804d3c970c5da41a5527882e8a956b42d /src | |
download | mate-system-monitor-f45852ab2a7126f354079499fc8b03976c0eab27.tar.bz2 mate-system-monitor-f45852ab2a7126f354079499fc8b03976c0eab27.tar.xz |
initial
Diffstat (limited to 'src')
53 files changed, 12258 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..8794e47 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,71 @@ +## Process this file with automake to produce Makefile.in + +INCLUDES = \ + -DPROCMAN_DATADIR=\""$(datadir)/procman/"\" \ + -DMATELOCALEDIR=\""$(datadir)/locale"\" \ + -DDATADIR=\""$(datadir)"\" \ + @PROCMAN_CFLAGS@ + +bin_PROGRAMS = mate-system-monitor + +mate_system_monitor_SOURCES = \ + argv.h argv.cpp \ + procman.cpp procman.h \ + interface.cpp interface.h \ + callbacks.cpp callbacks.h \ + load-graph.cpp load-graph.h \ + proctable.cpp proctable.h \ + prettytable.cpp prettytable.h \ + util.cpp util.h \ + procactions.cpp procactions.h \ + procdialogs.cpp procdialogs.h \ + memmaps.cpp memmaps.h \ + openfiles.cpp openfiles.h \ + smooth_refresh.cpp smooth_refresh.h \ + defaulttable.h \ + disks.cpp disks.h \ + selinux.h selinux.cpp \ + procman_matesu.h procman_matesu.cpp \ + procman_gksu.h procman_gksu.cpp \ + sysinfo.cpp sysinfo.h \ + lsof.cpp lsof.h \ + selection.cpp selection.h \ + mateconf-keys.cpp mateconf-keys.h \ + iconthemewrapper.cpp iconthemewrapper.h \ + e_date.c e_date.h \ + gsm_color_button.c gsm_color_button.h + + +mate_system_monitor_LDADD = @PROCMAN_LIBS@ libbacon.la + + +noinst_LTLIBRARIES = libbacon.la +libbacon_la_SOURCES = \ + bacon-message-connection.c \ + bacon-message-connection.h + + +specdir = $(datadir)/procman + +schemadir = $(MATECONF_SCHEMA_FILE_DIR) +schema_ins = mate-system-monitor.schemas.in +schema_DATA = $(schema_ins:.schemas.in=.schemas) +@INTLTOOL_SCHEMAS_RULE@ + +EXTRA_DIST = \ + $(schema_ins) + +CLEANFILES = \ + $(schema_DATA) + +if MATECONF_SCHEMAS_INSTALL +install-data-local: + if test -z "$(DESTDIR)"; then \ + for p in $(schema_DATA); do \ + MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) \ + mateconftool-2 --makefile-install-rule $(top_builddir)/src/$$p; \ + done; \ + fi +else +install-data-local: +endif diff --git a/src/argv.cpp b/src/argv.cpp new file mode 100644 index 0000000..a81a3c0 --- /dev/null +++ b/src/argv.cpp @@ -0,0 +1,22 @@ +#include <config.h> + +#include <glib/gi18n.h> +#include <glibmm/optionentry.h> + + +#include "argv.h" + +namespace procman +{ + OptionGroup::OptionGroup() + : Glib::OptionGroup("", ""), + show_system_tab(false) + { + Glib::OptionEntry sys_tab; + sys_tab.set_long_name("show-system-tab"); + sys_tab.set_short_name('s'); + sys_tab.set_description(_("Show the System tab")); + this->add_entry(sys_tab, this->show_system_tab); + } +} + diff --git a/src/argv.h b/src/argv.h new file mode 100644 index 0000000..a0f7205 --- /dev/null +++ b/src/argv.h @@ -0,0 +1,18 @@ +#ifndef H_PROCMAN_ARGV_1205873424 +#define H_PROCMAN_ARGV_1205873424 + +#include <glibmm/optiongroup.h> + +namespace procman +{ + class OptionGroup + : public Glib::OptionGroup + { + public: + OptionGroup(); + + bool show_system_tab; + }; +} + +#endif // H_PROCMAN_ARGV_1205873424 diff --git a/src/bacon-message-connection.c b/src/bacon-message-connection.c new file mode 100644 index 0000000..c8000de --- /dev/null +++ b/src/bacon-message-connection.c @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2003 Bastien Nocera <[email protected]> + * + * 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#include "bacon-message-connection.h" + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +struct BaconMessageConnection { + /* A server accepts connections */ + gboolean is_server; + + /* The socket path itself */ + char *path; + + /* File descriptor of the socket */ + int fd; + /* Channel to watch */ + GIOChannel *chan; + /* Event id returned by g_io_add_watch() */ + int conn_id; + + /* Connections accepted by this connection */ + GSList *accepted_connections; + + /* callback */ + void (*func) (const char *message, gpointer user_data); + gpointer data; +}; + +static gboolean +test_is_socket (const char *path) +{ + struct stat s; + + if (stat (path, &s) == -1) + return FALSE; + + if (S_ISSOCK (s.st_mode)) + return TRUE; + + return FALSE; +} + +static gboolean +is_owned_by_user_and_socket (const char *path) +{ + struct stat s; + + if (stat (path, &s) == -1) + return FALSE; + + if (s.st_uid != geteuid ()) + return FALSE; + + if ((s.st_mode & S_IFSOCK) != S_IFSOCK) + return FALSE; + + return TRUE; +} + +static gboolean server_cb (GIOChannel *source, + GIOCondition condition, gpointer data); + +static gboolean +setup_connection (BaconMessageConnection *conn) +{ + g_return_val_if_fail (conn->chan == NULL, FALSE); + + conn->chan = g_io_channel_unix_new (conn->fd); + if (!conn->chan) { + return FALSE; + } + g_io_channel_set_line_term (conn->chan, "\n", 1); + conn->conn_id = g_io_add_watch (conn->chan, G_IO_IN, server_cb, conn); + + return TRUE; +} + +static void +accept_new_connection (BaconMessageConnection *server_conn) +{ + BaconMessageConnection *conn; + int alen; + + g_return_if_fail (server_conn->is_server); + + conn = g_new0 (BaconMessageConnection, 1); + conn->is_server = FALSE; + conn->func = server_conn->func; + conn->data = server_conn->data; + + conn->fd = accept (server_conn->fd, NULL, (guint *)&alen); + + server_conn->accepted_connections = + g_slist_prepend (server_conn->accepted_connections, conn); + + setup_connection (conn); +} + +static gboolean +server_cb (GIOChannel *source, GIOCondition condition, gpointer data) +{ + BaconMessageConnection *conn = (BaconMessageConnection *)data; + char *message, *subs, buf; + int cd, rc, offset; + gboolean finished; + + offset = 0; + if (conn->is_server && conn->fd == g_io_channel_unix_get_fd (source)) { + accept_new_connection (conn); + return TRUE; + } + message = g_malloc (1); + cd = conn->fd; + rc = read (cd, &buf, 1); + while (rc > 0 && buf != '\n') + { + message = g_realloc (message, rc + offset + 1); + message[offset] = buf; + offset = offset + rc; + rc = read (cd, &buf, 1); + } + if (rc <= 0) { + g_io_channel_shutdown (conn->chan, FALSE, NULL); + g_io_channel_unref (conn->chan); + conn->chan = NULL; + close (conn->fd); + conn->fd = -1; + g_free (message); + conn->conn_id = 0; + + return FALSE; + } + message[offset] = '\0'; + + subs = message; + finished = FALSE; + + while (finished == FALSE && *subs != '\0') + { + if (conn->func != NULL) + (*conn->func) (subs, conn->data); + + subs += strlen (subs) + 1; + if (subs - message >= offset) + finished = TRUE; + } + + g_free (message); + + return TRUE; +} + +static char * +find_file_with_pattern (const char *dir, const char *pattern) +{ + GDir *filedir; + char *found_filename; + const char *filename; + GPatternSpec *pat; + + filedir = g_dir_open (dir, 0, NULL); + if (filedir == NULL) + return NULL; + + pat = g_pattern_spec_new (pattern); + if (pat == NULL) + { + g_dir_close (filedir); + return NULL; + } + + found_filename = NULL; + + while ((filename = g_dir_read_name (filedir))) + { + if (g_pattern_match_string (pat, filename)) + { + char *tmp = g_build_filename (dir, filename, NULL); + if (is_owned_by_user_and_socket (tmp)) + found_filename = g_strdup (filename); + g_free (tmp); + } + + if (found_filename != NULL) + break; + } + + g_pattern_spec_free (pat); + g_dir_close (filedir); + + return found_filename; +} + +static char * +socket_filename (const char *prefix) +{ + char *pattern, *newfile, *path, *filename; + const char *tmpdir; + + pattern = g_strdup_printf ("%s.%s.*", prefix, g_get_user_name ()); + tmpdir = g_get_tmp_dir (); + filename = find_file_with_pattern (tmpdir, pattern); + if (filename == NULL) + { + newfile = g_strdup_printf ("%s.%s.%u", prefix, + g_get_user_name (), g_random_int ()); + path = g_build_filename (tmpdir, newfile, NULL); + g_free (newfile); + } else { + path = g_build_filename (tmpdir, filename, NULL); + g_free (filename); + } + + g_free (pattern); + return path; +} + +static gboolean +try_server (BaconMessageConnection *conn) +{ + struct sockaddr_un uaddr; + + uaddr.sun_family = AF_UNIX; + strncpy (uaddr.sun_path, conn->path, + MIN (strlen(conn->path)+1, UNIX_PATH_MAX)); + conn->fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (bind (conn->fd, (struct sockaddr *) &uaddr, sizeof (uaddr)) == -1) + { + conn->fd = -1; + return FALSE; + } + listen (conn->fd, 5); + + if (!setup_connection (conn)) + return FALSE; + return TRUE; +} + +static gboolean +try_client (BaconMessageConnection *conn) +{ + struct sockaddr_un uaddr; + + uaddr.sun_family = AF_UNIX; + strncpy (uaddr.sun_path, conn->path, + MIN(strlen(conn->path)+1, UNIX_PATH_MAX)); + conn->fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (connect (conn->fd, (struct sockaddr *) &uaddr, + sizeof (uaddr)) == -1) + { + conn->fd = -1; + return FALSE; + } + + return setup_connection (conn); +} + +BaconMessageConnection * +bacon_message_connection_new (const char *prefix) +{ + BaconMessageConnection *conn; + + g_return_val_if_fail (prefix != NULL, NULL); + + conn = g_new0 (BaconMessageConnection, 1); + conn->path = socket_filename (prefix); + + if (test_is_socket (conn->path) == FALSE) + { + if (!try_server (conn)) + { + bacon_message_connection_free (conn); + return NULL; + } + + conn->is_server = TRUE; + return conn; + } + + if (try_client (conn) == FALSE) + { + unlink (conn->path); + try_server (conn); + if (conn->fd == -1) + { + bacon_message_connection_free (conn); + return NULL; + } + + conn->is_server = TRUE; + return conn; + } + + conn->is_server = FALSE; + return conn; +} + +void +bacon_message_connection_free (BaconMessageConnection *conn) +{ + GSList *child_conn; + + g_return_if_fail (conn != NULL); + /* Only servers can accept other connections */ + g_return_if_fail (conn->is_server != FALSE || + conn->accepted_connections == NULL); + + child_conn = conn->accepted_connections; + while (child_conn != NULL) { + bacon_message_connection_free (child_conn->data); + child_conn = g_slist_next (child_conn); + } + g_slist_free (conn->accepted_connections); + + if (conn->conn_id) { + g_source_remove (conn->conn_id); + conn->conn_id = 0; + } + if (conn->chan) { + g_io_channel_shutdown (conn->chan, FALSE, NULL); + g_io_channel_unref (conn->chan); + } + + if (conn->is_server != FALSE) { + unlink (conn->path); + } + if (conn->fd != -1) { + close (conn->fd); + } + + g_free (conn->path); + g_free (conn); +} + +void +bacon_message_connection_set_callback (BaconMessageConnection *conn, + BaconMessageReceivedFunc func, + gpointer user_data) +{ + g_return_if_fail (conn != NULL); + + conn->func = func; + conn->data = user_data; +} + +void +bacon_message_connection_send (BaconMessageConnection *conn, + const char *message) +{ + g_return_if_fail (conn != NULL); + g_return_if_fail (message != NULL); + + g_io_channel_write_chars (conn->chan, message, strlen (message), + NULL, NULL); + g_io_channel_write_chars (conn->chan, "\n", 1, NULL, NULL); + g_io_channel_flush (conn->chan, NULL); +} + +gboolean +bacon_message_connection_get_is_server (BaconMessageConnection *conn) +{ + g_return_val_if_fail (conn != NULL, FALSE); + + return conn->is_server; +} + diff --git a/src/bacon-message-connection.h b/src/bacon-message-connection.h new file mode 100644 index 0000000..aac7a2d --- /dev/null +++ b/src/bacon-message-connection.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003 Bastien Nocera <[email protected]> + * + * 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef BACON_MESSAGE_CONNECTION_H +#define BACON_MESSAGE_CONNECTION_H + +#include <glib.h> + +G_BEGIN_DECLS + +typedef void (*BaconMessageReceivedFunc) (const char *message, + gpointer user_data); + +typedef struct BaconMessageConnection BaconMessageConnection; + +BaconMessageConnection *bacon_message_connection_new (const char *prefix); +void bacon_message_connection_free (BaconMessageConnection *conn); +void bacon_message_connection_set_callback (BaconMessageConnection *conn, + BaconMessageReceivedFunc func, + gpointer user_data); +void bacon_message_connection_send (BaconMessageConnection *conn, + const char *message); +gboolean bacon_message_connection_get_is_server (BaconMessageConnection *conn); + +G_END_DECLS + +#endif /* BACON_MESSAGE_CONNECTION_H */ diff --git a/src/callbacks.cpp b/src/callbacks.cpp new file mode 100644 index 0000000..2acaff2 --- /dev/null +++ b/src/callbacks.cpp @@ -0,0 +1,436 @@ +/* Procman - callbacks + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <config.h> + +#include <giomm.h> + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <signal.h> + +#include "callbacks.h" +#include "interface.h" +#include "proctable.h" +#include "util.h" +#include "procactions.h" +#include "procdialogs.h" +#include "memmaps.h" +#include "openfiles.h" +#include "load-graph.h" +#include "disks.h" +#include "lsof.h" +#include "sysinfo.h" + +void +cb_kill_sigstop(GtkAction *action, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + /* no confirmation */ + kill_process (procdata, SIGSTOP); +} + + + + +void +cb_kill_sigcont(GtkAction *action, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + /* no confirmation */ + kill_process (procdata, SIGCONT); +} + + + + +static void +kill_process_helper(ProcData *procdata, int sig) +{ + if (procdata->config.show_kill_warning) + procdialog_create_kill_dialog (procdata, sig); + else + kill_process (procdata, sig); +} + + +void +cb_edit_preferences (GtkAction *action, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + procdialog_create_preferences_dialog (procdata); +} + + +void +cb_renice (GtkAction *action, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + procdialog_create_renice_dialog (procdata); +} + + +void +cb_end_process (GtkAction *action, gpointer data) +{ + kill_process_helper(static_cast<ProcData*>(data), SIGTERM); +} + + +void +cb_kill_process (GtkAction *action, gpointer data) +{ + kill_process_helper(static_cast<ProcData*>(data), SIGKILL); +} + + +void +cb_show_memory_maps (GtkAction *action, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + create_memmaps_dialog (procdata); +} + +void +cb_show_open_files (GtkAction *action, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + + create_openfiles_dialog (procdata); +} + +void +cb_show_lsof(GtkAction *action, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + procman_lsof(procdata); +} + + +void +cb_about (GtkAction *action, gpointer data) +{ + const gchar * const authors[] = { + "Kevin Vandersloot", + "Erik Johnsson", + "Jorgen Scheibengruber", + "Benoît Dejean", + "Paolo Borelli", + "Karl Lattimer", + NULL + }; + + const gchar * const documenters[] = { + "Bill Day", + "Sun Microsystems", + NULL + }; + + const gchar * const artists[] = { + "Baptiste Mille-Mathias", + NULL + }; + + gtk_show_about_dialog ( + NULL, + "name", _("System Monitor"), + "comments", _("View current processes and monitor " + "system state"), + "version", VERSION, + "copyright", "Copyright \xc2\xa9 2001-2004 Kevin Vandersloot\n" + "Copyright \xc2\xa9 2005-2007 Benoît Dejean", + "logo-icon-name", "utilities-system-monitor", + "authors", authors, + "artists", artists, + "documenters", documenters, + "translator-credits", _("translator-credits"), + "license", "GPL 2+", + "wrap-license", TRUE, + NULL + ); +} + + +void +cb_help_contents (GtkAction *action, gpointer data) +{ + GError* error = 0; + if (!g_app_info_launch_default_for_uri("ghelp:mate-system-monitor", NULL, &error)) { + g_warning("Could not display help : %s", error->message); + g_error_free(error); + } +} + + +void +cb_app_exit (GtkAction *action, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + cb_app_delete (NULL, NULL, procdata); +} + + +gboolean +cb_app_delete (GtkWidget *window, GdkEventAny *event, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + procman_save_config (procdata); + if (procdata->timeout) + g_source_remove (procdata->timeout); + if (procdata->disk_timeout) + g_source_remove (procdata->disk_timeout); + + gtk_main_quit (); + + return TRUE; +} + + + +void +cb_end_process_button_pressed (GtkButton *button, gpointer data) +{ + kill_process_helper(static_cast<ProcData*>(data), SIGTERM); +} + + +static void change_mateconf_color(MateConfClient *client, const char *key, + GSMColorButton *cp) +{ + GdkColor c; + char color[24]; /* color should be 1 + 3*4 + 1 = 15 chars -> 24 */ + + gsm_color_button_get_color(cp, &c); + g_snprintf(color, sizeof color, "#%04x%04x%04x", c.red, c.green, c.blue); + mateconf_client_set_string (client, key, color, NULL); +} + + +void +cb_cpu_color_changed (GSMColorButton *cp, gpointer data) +{ + char key[80]; + gint i = GPOINTER_TO_INT (data); + MateConfClient *client = mateconf_client_get_default (); + + g_snprintf(key, sizeof key, "/apps/procman/cpu_color%d", i); + + change_mateconf_color(client, key, cp); +} + +void +cb_mem_color_changed (GSMColorButton *cp, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + change_mateconf_color(procdata->client, "/apps/procman/mem_color", cp); +} + + +void +cb_swap_color_changed (GSMColorButton *cp, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + change_mateconf_color(procdata->client, "/apps/procman/swap_color", cp); +} + +void +cb_net_in_color_changed (GSMColorButton *cp, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + change_mateconf_color(procdata->client, "/apps/procman/net_in_color", cp); +} + +void +cb_net_out_color_changed (GSMColorButton *cp, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + change_mateconf_color(procdata->client, "/apps/procman/net_out_color", cp); +} + +static void +get_last_selected (GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + ProcInfo **info = static_cast<ProcInfo**>(data); + + gtk_tree_model_get (model, iter, COL_POINTER, info, -1); +} + + +void +cb_row_selected (GtkTreeSelection *selection, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + procdata->selection = selection; + + /* get the most recent selected process and determine if there are + ** no selected processes + */ + gtk_tree_selection_selected_foreach (procdata->selection, get_last_selected, + &procdata->selected_process); + + update_sensitivity(procdata); +} + + +gboolean +cb_tree_button_pressed (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + do_popup_menu (procdata, event); + + return FALSE; +} + + +gboolean +cb_tree_popup_menu (GtkWidget *widget, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + do_popup_menu (procdata, NULL); + + return TRUE; +} + + +void +cb_switch_page (GtkNotebook *nb, GtkNotebookPage *page, + gint num, gpointer data) +{ + cb_change_current_page (nb, num, data); +} + +void +cb_change_current_page (GtkNotebook *nb, gint num, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + procdata->config.current_tab = num; + + + if (num == PROCMAN_TAB_PROCESSES) { + + cb_timeout (procdata); + + if (!procdata->timeout) + procdata->timeout = g_timeout_add ( + procdata->config.update_interval, + cb_timeout, procdata); + + update_sensitivity(procdata); + } + else { + if (procdata->timeout) { + g_source_remove (procdata->timeout); + procdata->timeout = 0; + } + + update_sensitivity(procdata); + } + + + if (num == PROCMAN_TAB_RESOURCES) { + load_graph_start (procdata->cpu_graph); + load_graph_start (procdata->mem_graph); + load_graph_start (procdata->net_graph); + } + else { + load_graph_stop (procdata->cpu_graph); + load_graph_stop (procdata->mem_graph); + load_graph_stop (procdata->net_graph); + } + + + if (num == PROCMAN_TAB_DISKS) { + + cb_update_disks (procdata); + + if(!procdata->disk_timeout) { + procdata->disk_timeout = + g_timeout_add (procdata->config.disks_update_interval, + cb_update_disks, + procdata); + } + } + else { + if(procdata->disk_timeout) { + g_source_remove (procdata->disk_timeout); + procdata->disk_timeout = 0; + } + } + + if (num == PROCMAN_TAB_SYSINFO) { + procman::build_sysinfo_ui(); + } +} + + + +gint +cb_user_refresh (GtkAction*, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + proctable_update_all(procdata); + return FALSE; +} + + +gint +cb_timeout (gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + guint new_interval; + + proctable_update_all (procdata); + + if (procdata->smooth_refresh->get(new_interval)) + { + procdata->timeout = g_timeout_add(new_interval, + cb_timeout, + procdata); + return FALSE; + } + + return TRUE; +} + + +void +cb_radio_processes(GtkAction *action, GtkRadioAction *current, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + + procdata->config.whose_process = gtk_radio_action_get_current_value(current); + + mateconf_client_set_int (procdata->client, "/apps/procman/view_as", + procdata->config.whose_process, NULL); +} diff --git a/src/callbacks.h b/src/callbacks.h new file mode 100644 index 0000000..4c84793 --- /dev/null +++ b/src/callbacks.h @@ -0,0 +1,82 @@ +/* Procman - callbacks + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef _PROCMAN_CALLBACKS_H_ +#define _PROCMAN_CALLBACKS_H_ + +#include <gtk/gtk.h> +#include "procman.h" +#include "gsm_color_button.h" + + +void cb_show_memory_maps (GtkAction *action, gpointer data); +void cb_show_open_files (GtkAction *action, gpointer data); +void cb_show_lsof(GtkAction *action, gpointer data); +void cb_renice (GtkAction *action, gpointer data); +void cb_end_process (GtkAction *action, gpointer data); +void cb_kill_process (GtkAction *action, gpointer data); +void cb_edit_preferences (GtkAction *action, gpointer data); + +void cb_help_contents (GtkAction *action, gpointer data); +void cb_about (GtkAction *action, gpointer data); + +void cb_app_exit (GtkAction *action, gpointer data); +gboolean cb_app_delete (GtkWidget *window, GdkEventAny *event, gpointer data); + +void cb_end_process_button_pressed (GtkButton *button, gpointer data); +void cb_logout (GtkButton *button, gpointer data); + +void cb_info_button_pressed (GtkButton *button, gpointer user_data); + +void cb_cpu_color_changed (GSMColorButton *widget, gpointer user_data); +void cb_mem_color_changed (GSMColorButton *widget, gpointer user_data); +void cb_swap_color_changed (GSMColorButton *widget, gpointer user_data); +void cb_net_in_color_changed (GSMColorButton *widget, gpointer user_data); +void cb_net_out_color_changed (GSMColorButton *widget, gpointer user_data); + +void cb_row_selected (GtkTreeSelection *selection, gpointer data); + +gboolean cb_tree_popup_menu (GtkWidget *widget, gpointer data); +gboolean cb_tree_button_pressed (GtkWidget *widget, GdkEventButton *event, + gpointer data); + + +void cb_change_current_page (GtkNotebook *nb, + gint num, gpointer data); +void cb_switch_page (GtkNotebook *nb, GtkNotebookPage *page, + gint num, gpointer data); + +gint cb_update_disks (gpointer data); +gint cb_user_refresh (GtkAction* action, gpointer data); +gint cb_timeout (gpointer data); + +void cb_radio_processes(GtkAction *action, + GtkRadioAction *current, + gpointer data); + + + +void cb_kill_sigstop(GtkAction *action, + gpointer data); + +void cb_kill_sigcont(GtkAction *action, + gpointer data); + +#endif /* _PROCMAN_CALLBACKS_H_ */ diff --git a/src/defaulttable.h b/src/defaulttable.h new file mode 100644 index 0000000..6b5a096 --- /dev/null +++ b/src/defaulttable.h @@ -0,0 +1,51 @@ +#ifndef _PROCMAN_DEFAULTTABLE_H_ +#define _PROCMAN_DEFAULTTABLE_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 + +/* The current table is only a test */ +static const PrettyTableItem default_table[] = { + 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(".*applet(-?2)?", "mate-applets"), + ITEM("atd|cron|CRON|ntpd", "date"), + ITEM("cupsd|lpd?", "printer"), + ITEM("cvsd|mtn|git|svn", "file-manager"), + ITEM("emacs(server|\\d+)?", "mate-emacs"), + ITEM("evolution.*", "internet-mail"), + ITEM("famd|gam_server", "file-manager"), + ITEM("mateconfd-2", "preferences-desktop"), + ITEM("getty", "input-keyboard"), + ITEM("gdb|((gcc|g\\+\\+)(-.*)?)|ar|ld|make", "applications-development"), + ITEM("marco", "mate-window-manager"), + ITEM("sendmail|exim\\d?", "internet-mail"), + ITEM("squid", "proxy"), + ITEM("ssh(d|-agent)", "ssh-askpass-mate"), + ITEM("top|vmstat", "system-monitor"), + ITEM("vim?", "vim"), + ITEM("x?inetd", "internet-web-browser"), + ITEM("vino.*", "mate-remote-desktop") +}; + +#undef ITEM + +#endif /* _PROCMAN_DEFAULTTABLE_H_ */ diff --git a/src/disks.cpp b/src/disks.cpp new file mode 100644 index 0000000..172fc7a --- /dev/null +++ b/src/disks.cpp @@ -0,0 +1,425 @@ +#include <config.h> + +#include <giomm.h> +#include <giomm/themedicon.h> +#include <gtk/gtk.h> +#include <glibtop/mountlist.h> +#include <glibtop/fsusage.h> +#include <glib/gi18n.h> + +#include "procman.h" +#include "disks.h" +#include "util.h" +#include "interface.h" +#include "iconthemewrapper.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 unvisible columns + /* PixBuf column */ + DISK_ICON, + /* numeric columns */ + DISK_USED_PERCENTAGE, + DISK_N_COLUMNS +}; + + + +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); + } +} + + +namespace +{ + string get_icon_for_path(const std::string& path) + { + using namespace Glib; + using namespace Gio; + + // FIXME: I don't know whether i should use Volume or Mount or UnixMount + // all i need an icon name. + RefPtr<VolumeMonitor> monitor = VolumeMonitor::get(); + + std::vector<RefPtr<Mount> > mounts = monitor->get_mounts(); + + for (size_t i = 0; i != mounts.size(); ++i) { + if (mounts[i]->get_name() != path) + continue; + + RefPtr<Icon> icon = mounts[i]->get_icon(); + RefPtr<ThemedIcon> themed_icon = RefPtr<ThemedIcon>::cast_dynamic(icon); + + if (themed_icon) { + char* name = 0; + // FIXME: not wrapped yet + g_object_get(G_OBJECT(themed_icon->gobj()), "name", &name, NULL); + return make_string(name); + } + } + + return ""; + } +} + + +static Glib::RefPtr<Gdk::Pixbuf> +get_icon_for_device(const char *mountpoint) +{ + procman::IconThemeWrapper icon_theme; + string icon_name = get_icon_for_path(mountpoint); + if (icon_name == "") + // FIXME: defaults to a safe value + icon_name = "drive-harddisk"; // get_icon_for_path("/"); + return icon_theme->load_icon(icon_name, 24, Gtk::ICON_LOOKUP_USE_BUILTIN); +} + + +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) +{ + Glib::RefPtr<Gdk::Pixbuf> 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->gobj(), + 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); +} + + + +int +cb_update_disks(gpointer data) +{ + ProcData *const procdata = static_cast<ProcData*>(data); + + GtkListStore *list; + glibtop_mountentry * entries; + glibtop_mountlist mountlist; + guint i; + + list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(procdata->disk_list))); + + entries = glibtop_get_mountlist(&mountlist, procdata->config.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], procdata->config.show_all_fs); + + g_free(entries); + + return TRUE; +} + + +static void +cb_disk_columns_changed(GtkTreeView *treeview, gpointer user_data) +{ + ProcData * const procdata = static_cast<ProcData*>(user_data); + + procman_save_tree_state(procdata->client, + GTK_WIDGET(treeview), + "/apps/procman/disktreenew"); +} + + +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); +} + +GtkWidget * +create_disk_view(ProcData *procdata) +{ + GtkWidget *disk_box; + GtkWidget *label; + GtkWidget *scrolled; + GtkWidget *disk_tree; + GtkListStore *model; + GtkTreeViewColumn *col; + GtkCellRenderer *cell; + guint i; + + const gchar * const titles[] = { + N_("Device"), + N_("Directory"), + N_("Type"), + N_("Total"), + N_("Free"), + N_("Available"), + N_("Used") + }; + + disk_box = gtk_vbox_new(FALSE, 6); + + gtk_container_set_border_width(GTK_CONTAINER(disk_box), 12); + + label = make_title_label(_("File Systems")); + gtk_box_pack_start(GTK_BOX(disk_box), label, FALSE, FALSE, 0); + + scrolled = 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_box_pack_start(GTK_BOX(disk_box), scrolled, TRUE, TRUE, 0); + + 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 = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + g_signal_connect(G_OBJECT(disk_tree), "row-activated", G_CALLBACK(open_dir), NULL); + procdata->disk_list = disk_tree; + gtk_container_add(GTK_CONTAINER(scrolled), disk_tree); + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(disk_tree), TRUE); + 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_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_append_column(GTK_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_append_column(GTK_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_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_pack_start(col, cell, FALSE); + gtk_tree_view_column_set_cell_data_func(col, cell, + &procman::size_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_tree_view_column_pack_start(col, cell, TRUE); + gtk_tree_view_column_set_attributes(col, cell, "value", + DISK_USED_PERCENTAGE, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(disk_tree), col); + 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); + + /* numeric sort */ + + gtk_widget_show_all(disk_box); + + procman_get_tree_state(procdata->client, disk_tree, + "/apps/procman/disktreenew"); + + g_signal_connect (G_OBJECT(disk_tree), "columns-changed", + G_CALLBACK(cb_disk_columns_changed), procdata); + + return disk_box; +} diff --git a/src/disks.h b/src/disks.h new file mode 100644 index 0000000..c62c47a --- /dev/null +++ b/src/disks.h @@ -0,0 +1,12 @@ +#ifndef H_MATE_SYSTEM_MONITOR_DISKS_1123719137 +#define H_MATE_SYSTEM_MONITOR_DISKS_1123719137 + +#include "procman.h" + +GtkWidget * +create_disk_view(ProcData *procdata); + +int +cb_update_disks(gpointer procdata); + +#endif /* H_MATE_SYSTEM_MONITOR_DISKLIST_1123719137 */ diff --git a/src/e_date.c b/src/e_date.c new file mode 100644 index 0000000..9b9a894 --- /dev/null +++ b/src/e_date.c @@ -0,0 +1,214 @@ +#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/e_date.h b/src/e_date.h new file mode 100644 index 0000000..28dfd13 --- /dev/null +++ b/src/e_date.h @@ -0,0 +1,9 @@ +#ifndef H_PROCMAN_E_DATE_1135695432 +#define H_PROCMAN_E_DATE_1135695432 + +#include <time.h> + +char * +procman_format_date_for_display(time_t d); + +#endif /* H_PROCMAN_E_DATE_1135695432 */ diff --git a/src/gsm_color_button.c b/src/gsm_color_button.c new file mode 100644 index 0000000..ce72cc1 --- /dev/null +++ b/src/gsm_color_button.c @@ -0,0 +1,923 @@ +/* + * Mate system monitor colour pickers + * Copyright (C) 2007 Karl Lattimer <[email protected]> + * 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, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#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 <cairo.h> + +#include "gsm_color_button.h" + +#define GSM_COLOR_BUTTON_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GSM_TYPE_COLOR_BUTTON, GSMColorButtonPrivate)) + +struct _GSMColorButtonPrivate +{ + GtkWidget *cs_dialog; /* Color selection dialog */ + + gchar *title; /* Title for the color selection window */ + + GdkColor color; + + gdouble fraction; /* Only used by GSMCP_TYPE_PIE */ + guint type; + cairo_surface_t *image_buffer; + gdouble highlight; +}; + +/* 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_intern_init (gpointer); +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 void gsm_color_button_realize (GtkWidget * widget); +static void gsm_color_button_size_request (GtkWidget * widget, + GtkRequisition * requisition); +static void gsm_color_button_size_allocate (GtkWidget * widget, + GtkAllocation * allocation); +static void gsm_color_button_unrealize (GtkWidget * widget); +static void gsm_color_button_state_changed (GtkWidget * widget, + GtkStateType previous_state); +static void gsm_color_button_style_set (GtkWidget * widget, + GtkStyle * previous_style); +static gint gsm_color_button_clicked (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 gpointer gsm_color_button_parent_class = NULL; + +static const GtkTargetEntry drop_types[] = { {"application/x-color", 0, 0} }; + +GType +gsm_color_button_get_type () +{ + static GType gsm_color_button_type = 0; + + if (!gsm_color_button_type) + { + static const GTypeInfo gsm_color_button_info = { + sizeof (GSMColorButtonClass), + NULL, + NULL, + (GClassInitFunc) gsm_color_button_class_intern_init, + NULL, + NULL, + sizeof (GSMColorButton), + 0, + (GInstanceInitFunc) gsm_color_button_init, + }; + + gsm_color_button_type = + g_type_register_static (GTK_TYPE_DRAWING_AREA, "GSMColorButton", + &gsm_color_button_info, 0); + } + + return gsm_color_button_type; +} + +static void +gsm_color_button_class_intern_init (gpointer klass) +{ + gsm_color_button_parent_class = g_type_class_peek_parent (klass); + gsm_color_button_class_init ((GSMColorButtonClass *) klass); +} + +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->state_changed = gsm_color_button_state_changed; + widget_class->size_request = gsm_color_button_size_request; + widget_class->size_allocate = gsm_color_button_size_allocate; + widget_class->realize = gsm_color_button_realize; + widget_class->unrealize = gsm_color_button_unrealize; + widget_class->style_set = gsm_color_button_style_set; + widget_class->button_release_event = gsm_color_button_clicked; + widget_class->enter_notify_event = gsm_color_button_enter_notify; + widget_class->leave_notify_event = gsm_color_button_leave_notify; + + klass->color_set = NULL; + + g_object_class_install_property (gobject_class, + PROP_PERCENTAGE, + g_param_spec_double ("fraction", + _("Fraction"), + _("Percentage full for pie colour 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_COLOR, + 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, + G_STRUCT_OFFSET + (GSMColorButtonClass, + color_set), NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (gobject_class, sizeof (GSMColorButtonPrivate)); +} + + +static cairo_surface_t * +fill_image_buffer_from_file (cairo_t *cr, const char *filePath) +{ + GError *error = NULL; + RsvgHandle *handle; + cairo_surface_t *tmp_surface; + cairo_t *tmp_cr; + + handle = rsvg_handle_new_from_file (filePath, &error); + + if (handle == NULL) { + g_warning("rsvg_handle_new_from_file(\"%s\") failed: %s", + filePath, (error ? error->message : "unknown error")); + if (error) + g_error_free(error); + 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); + rsvg_handle_free (handle); + return tmp_surface; +} + + +static void +render (GtkWidget * widget) +{ + GSMColorButton *color_button = GSM_COLOR_BUTTON (widget); + GdkColor *color, tmp_color = color_button->priv->color; + color = &tmp_color; + cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget)); + cairo_path_t *path = NULL; + gint width, height; + gdouble radius, arc_start, arc_end; + gint highlight_factor; + + if (color_button->priv->highlight > 0) { + highlight_factor = 8192 * color_button->priv->highlight; + + if (color->red + highlight_factor > 65535) + color->red = 65535; + else + color->red = color->red + highlight_factor; + + if (color->blue + highlight_factor > 65535) + color->blue = 65535; + else + color->blue = color->blue + highlight_factor; + + if (color->green + highlight_factor > 65535) + color->green = 65535; + else + color->green = color->green + highlight_factor; + } + gdk_cairo_set_source_color (cr, color); + + #if GTK_CHECK_VERSION(3, 0, 0) + width = gdk_window_get_width(gtk_widget_get_window(widget)); + height = gdk_window_get_height(gtk_widget_get_window(widget)); + #else + gdk_drawable_get_size(gtk_widget_get_window(widget), &width, &height); + #endif + + + switch (color_button->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 * color_button->priv->fraction; + arc_end = -G_PI_2; + + cairo_set_line_width (cr, 1); + + // Draw external stroke and fill + if (color_button->priv->fraction < 0.01) { + cairo_arc (cr, (width / 2) + .5, (height / 2) + .5, 4.5, + 0, 2 * G_PI); + } else if (color_button->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 (color_button->priv->fraction < 0.03) { + cairo_arc (cr, (width / 2) + .5, (height / 2) + .5, 3.25, + 0, 2 * G_PI); + } else if (color_button->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 (color_button->priv->image_buffer == NULL) + color_button->priv->image_buffer = + fill_image_buffer_from_file (cr, DATADIR "/pixmaps/mate-system-monitor/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, color_button->priv->image_buffer, 0.0, + 0.0); + cairo_paint (cr); + + break; + case GSMCP_TYPE_NETWORK_OUT: + if (color_button->priv->image_buffer == NULL) + color_button->priv->image_buffer = + fill_image_buffer_from_file (cr, DATADIR "/pixmaps/mate-system-monitor/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, color_button->priv->image_buffer, 0.0, + 0.0); + cairo_paint (cr); + + break; + } + cairo_destroy (cr); +} + +/* Handle exposure events for the color picker's drawing area */ +static gint +expose_event (GtkWidget * widget, GdkEventExpose * event, gpointer data) +{ + render (GTK_WIDGET (data)); + + return FALSE; +} + +static void +gsm_color_button_realize (GtkWidget * widget) +{ + GTK_WIDGET_CLASS (gsm_color_button_parent_class)->realize (widget); + render (widget); +} + +static void +gsm_color_button_size_request (GtkWidget * widget, + GtkRequisition * requisition) +{ + g_return_if_fail (widget != NULL || requisition != NULL); + g_return_if_fail (GSM_IS_COLOR_BUTTON (widget)); + + requisition->width = GSMCP_MIN_WIDTH; + requisition->height = GSMCP_MIN_HEIGHT; +} + +static void +gsm_color_button_size_allocate (GtkWidget * widget, + GtkAllocation * allocation) +{ + GSMColorButton *color_button; + + g_return_if_fail (widget != NULL || allocation != NULL); + g_return_if_fail (GSM_IS_COLOR_BUTTON (widget)); + + gtk_widget_set_allocation (widget, allocation); + color_button = GSM_COLOR_BUTTON (widget); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (gtk_widget_get_window (widget), allocation->x, allocation->y, + allocation->width, allocation->height); + } +} + +static void +gsm_color_button_unrealize (GtkWidget * widget) +{ + + GTK_WIDGET_CLASS (gsm_color_button_parent_class)->unrealize (widget); +} + +static void +gsm_color_button_style_set (GtkWidget * widget, GtkStyle * previous_style) +{ + + GTK_WIDGET_CLASS (gsm_color_button_parent_class)->style_set (widget, + previous_style); + +} + +static void +gsm_color_button_state_changed (GtkWidget * widget, + GtkStateType previous_state) +{ +} + +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) +{ + 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); + + color_button->priv->color.red = dropped[0]; + color_button->priv->color.green = dropped[1]; + color_button->priv->color.blue = dropped[2]; + + gtk_widget_queue_draw (GTK_WIDGET (&color_button->widget)); + + 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, GdkColor * color) +{ + GdkPixbuf *pixbuf; + guint32 pixel; + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 48, 32); + + pixel = ((color->red & 0xff00) << 16) | + ((color->green & 0xff00) << 8) | (color->blue & 0xff00); + + 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) +{ + GSMColorButton *color_button = data; + + set_color_icon (context, &color_button->priv->color); +} + +static void +gsm_color_button_drag_data_get (GtkWidget * widget, + GdkDragContext * context, + GtkSelectionData * selection_data, + guint info, + guint time, GSMColorButton * color_button) +{ + guint16 dropped[4]; + + dropped[0] = color_button->priv->color.red; + dropped[1] = color_button->priv->color.green; + dropped[2] = color_button->priv->color.blue; + 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) +{ + color_button->priv = GSM_COLOR_BUTTON_GET_PRIVATE (color_button); + + rsvg_init (); + + color_button->priv->color.red = 0; + color_button->priv->color.green = 0; + color_button->priv->color.blue = 0; + color_button->priv->fraction = 0.5; + color_button->priv->type = GSMCP_TYPE_CPU; + color_button->priv->image_buffer = NULL; + color_button->priv->title = g_strdup (_("Pick a Color")); /* default title */ + + 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")); + + g_signal_connect (color_button, "expose-event", + G_CALLBACK (expose_event), color_button); +} + +static void +gsm_color_button_finalize (GObject * object) +{ + GSMColorButton *color_button = GSM_COLOR_BUTTON (object); + + if (color_button->priv->cs_dialog != NULL) + gtk_widget_destroy (color_button->priv->cs_dialog); + color_button->priv->cs_dialog = NULL; + + g_free (color_button->priv->title); + color_button->priv->title = NULL; + + cairo_surface_destroy (color_button->priv->image_buffer); + color_button->priv->image_buffer = NULL; + + rsvg_term (); + G_OBJECT_CLASS (gsm_color_button_parent_class)->finalize (object); +} + +GtkWidget * +gsm_color_button_new (const GdkColor * color, guint type) +{ + return g_object_new (GSM_TYPE_COLOR_BUTTON, "color", color, "type", type, + NULL); +} + +static void +dialog_response (GtkWidget * widget, GtkResponseType response, gpointer data) +{ + GSMColorButton *color_button = GSM_COLOR_BUTTON (data); + GtkColorSelection *color_selection; + + if (response == GTK_RESPONSE_OK) { + color_selection = + GTK_COLOR_SELECTION (gtk_color_selection_dialog_get_color_selection (GTK_COLOR_SELECTION_DIALOG + (color_button->priv->cs_dialog))); + + gtk_color_selection_get_current_color (color_selection, + &color_button->priv->color); + + gtk_widget_hide (color_button->priv->cs_dialog); + + gtk_widget_queue_draw (GTK_WIDGET (&color_button->widget)); + + 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 (color_button->priv->cs_dialog); +} + +static gboolean +dialog_destroy (GtkWidget * widget, gpointer data) +{ + GSMColorButton *color_button = GSM_COLOR_BUTTON (data); + + color_button->priv->cs_dialog = NULL; + + return FALSE; +} + +static gint +gsm_color_button_clicked (GtkWidget * widget, GdkEventButton * event) +{ + GSMColorButton *color_button = GSM_COLOR_BUTTON (widget); + GtkColorSelectionDialog *color_dialog; + + /* if dialog already exists, make sure it's shown and raised */ + if (!color_button->priv->cs_dialog) + { + /* Create the dialog and connects its buttons */ + GtkWidget *parent; + + parent = gtk_widget_get_toplevel (GTK_WIDGET (color_button)); + + color_button->priv->cs_dialog = + gtk_color_selection_dialog_new (color_button->priv->title); + + color_dialog = + GTK_COLOR_SELECTION_DIALOG (color_button->priv->cs_dialog); + + if (gtk_widget_is_toplevel (parent) && GTK_IS_WINDOW (parent)) + { + if (GTK_WINDOW (parent) != + gtk_window_get_transient_for (GTK_WINDOW (color_dialog))) + gtk_window_set_transient_for (GTK_WINDOW (color_dialog), + GTK_WINDOW (parent)); + + gtk_window_set_modal (GTK_WINDOW (color_dialog), + gtk_window_get_modal (GTK_WINDOW (parent))); + } + + g_signal_connect (color_dialog, "response", + G_CALLBACK (dialog_response), color_button); + + g_signal_connect (color_dialog, "destroy", + G_CALLBACK (dialog_destroy), color_button); + } + + color_dialog = GTK_COLOR_SELECTION_DIALOG (color_button->priv->cs_dialog); + + gtk_color_selection_set_previous_color (GTK_COLOR_SELECTION + (gtk_color_selection_dialog_get_color_selection (color_dialog)), + &color_button->priv->color); + + gtk_color_selection_set_current_color (GTK_COLOR_SELECTION + (gtk_color_selection_dialog_get_color_selection (color_dialog)), + &color_button->priv->color); + + gtk_window_present (GTK_WINDOW (color_button->priv->cs_dialog)); + return 0; +} + +static gboolean +gsm_color_button_enter_notify (GtkWidget * widget, GdkEventCrossing * event) +{ + GSMColorButton *color_button = GSM_COLOR_BUTTON (widget); + color_button->priv->highlight = 1.0; + gtk_widget_queue_draw(widget); + return FALSE; +} + +static gboolean +gsm_color_button_leave_notify (GtkWidget * widget, GdkEventCrossing * event) +{ + GSMColorButton *color_button = GSM_COLOR_BUTTON (widget); + color_button->priv->highlight = 0; + gtk_widget_queue_draw(widget); + return FALSE; +} + +guint +gsm_color_button_get_cbtype (GSMColorButton * color_button) +{ + g_return_val_if_fail (GSM_IS_COLOR_BUTTON (color_button), 0); + + return color_button->priv->type; +} + +void +gsm_color_button_set_cbtype (GSMColorButton * color_button, guint type) +{ + g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button)); + + color_button->priv->type = type; + + gtk_widget_queue_draw (GTK_WIDGET (&color_button->widget)); + + g_object_notify (G_OBJECT (color_button), "type"); +} + +gdouble +gsm_color_button_get_fraction (GSMColorButton * color_button) +{ + g_return_val_if_fail (GSM_IS_COLOR_BUTTON (color_button), 0); + + return color_button->priv->fraction; +} + +void +gsm_color_button_set_fraction (GSMColorButton * color_button, + gdouble fraction) +{ + g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button)); + + color_button->priv->fraction = fraction; + + gtk_widget_queue_draw (GTK_WIDGET (&color_button->widget)); + + g_object_notify (G_OBJECT (color_button), "fraction"); +} + +void +gsm_color_button_get_color (GSMColorButton * color_button, GdkColor * color) +{ + g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button)); + + color->red = color_button->priv->color.red; + color->green = color_button->priv->color.green; + color->blue = color_button->priv->color.blue; +} + +void +gsm_color_button_set_color (GSMColorButton * color_button, + const GdkColor * color) +{ + g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button)); + g_return_if_fail (color != NULL); + + color_button->priv->color.red = color->red; + color_button->priv->color.green = color->green; + color_button->priv->color.blue = color->blue; + + gtk_widget_queue_draw (GTK_WIDGET (&color_button->widget)); //->priv->draw_area); + + g_object_notify (G_OBJECT (color_button), "color"); +} + +void +gsm_color_button_set_title (GSMColorButton * color_button, + const gchar * title) +{ + gchar *old_title; + + g_return_if_fail (GSM_IS_COLOR_BUTTON (color_button)); + + old_title = color_button->priv->title; + color_button->priv->title = g_strdup (title); + g_free (old_title); + + if (color_button->priv->cs_dialog) + gtk_window_set_title (GTK_WINDOW (color_button->priv->cs_dialog), + color_button->priv->title); + + g_object_notify (G_OBJECT (color_button), "title"); +} + +const gchar* gsm_color_button_get_title(GSMColorButton* color_button) +{ + g_return_val_if_fail(GSM_IS_COLOR_BUTTON(color_button), NULL); + + return color_button->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); + GdkColor 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; + } +} + +#define __GSM_COLOR_BUTTON_C__ diff --git a/src/gsm_color_button.h b/src/gsm_color_button.h new file mode 100644 index 0000000..5effbc8 --- /dev/null +++ b/src/gsm_color_button.h @@ -0,0 +1,92 @@ +/* + * Mate system monitor colour pickers + * Copyright (C) 2007 Karl Lattimer <[email protected]> + * 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, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GSM_COLOR_BUTTON_H__ +#define __GSM_COLOR_BUTTON_H__ + +#include <glib.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#include <cairo.h> +#include <librsvg/rsvg.h> +#include <librsvg/rsvg-cairo.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; +typedef struct _GSMColorButtonPrivate GSMColorButtonPrivate; + +struct _GSMColorButton +{ + GtkDrawingArea widget; + + /*< private > */ + + GSMColorButtonPrivate *priv; +}; + +/* Widget types */ +enum +{ + GSMCP_TYPE_CPU, + GSMCP_TYPE_PIE, + GSMCP_TYPE_NETWORK_IN, + GSMCP_TYPE_NETWORK_OUT, + GSMCP_TYPES +}; + +struct _GSMColorButtonClass +{ + GtkWidgetClass parent_class; + + void (*color_set) (GSMColorButton * cp); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType gsm_color_button_get_type (void) G_GNUC_CONST; +GtkWidget *gsm_color_button_new (const GdkColor * color, guint type); +void gsm_color_button_set_color (GSMColorButton * color_button, const GdkColor * 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, GdkColor * 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); +const gchar* gsm_color_button_get_title(GSMColorButton* color_button); + +G_END_DECLS +#endif /* __GSM_COLOR_BUTTON_H__ */ diff --git a/src/iconthemewrapper.cpp b/src/iconthemewrapper.cpp new file mode 100644 index 0000000..496dd45 --- /dev/null +++ b/src/iconthemewrapper.cpp @@ -0,0 +1,23 @@ +#include <config.h> + +#include <gtkmm/icontheme.h> + +#include "iconthemewrapper.h" + + +Glib::RefPtr<Gdk::Pixbuf> +procman::IconThemeWrapper::load_icon(const Glib::ustring& icon_name, + int size, Gtk::IconLookupFlags flags) const +{ + try + { + return Gtk::IconTheme::get_default()->load_icon(icon_name, size, flags); + } + catch (Gtk::IconThemeError &error) + { + if (error.code() != Gtk::IconThemeError::ICON_THEME_NOT_FOUND) + g_error("Cannot load icon '%s' from theme: %s", icon_name.c_str(), error.what().c_str()); + return Glib::RefPtr<Gdk::Pixbuf>(); + } +} + diff --git a/src/iconthemewrapper.h b/src/iconthemewrapper.h new file mode 100644 index 0000000..6127f01 --- /dev/null +++ b/src/iconthemewrapper.h @@ -0,0 +1,23 @@ +#ifndef H_PROCMAN_ICONTHEMEWRAPPER_H_1185707711 +#define H_PROCMAN_ICONTHEMEWRAPPER_H_1185707711 + +#include <glibmm/refptr.h> +#include <glibmm/ustring.h> +#include <gtkmm/icontheme.h> +#include <gdkmm/pixbuf.h> + +namespace procman +{ + class IconThemeWrapper + { + public: + // returns 0 instead of raising an exception + Glib::RefPtr<Gdk::Pixbuf> + load_icon(const Glib::ustring& icon_name, int size, Gtk::IconLookupFlags flags) const; + + const IconThemeWrapper* operator->() const + { return this; } + }; +} + +#endif // H_PROCMAN_ICONTHEMEWRAPPER_H_1185707711 diff --git a/src/interface.cpp b/src/interface.cpp new file mode 100644 index 0000000..2a88676 --- /dev/null +++ b/src/interface.cpp @@ -0,0 +1,808 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + + +#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 "procman.h" +#include "callbacks.h" +#include "interface.h" +#include "proctable.h" +#include "procactions.h" +#include "load-graph.h" +#include "util.h" +#include "disks.h" +#include "sysinfo.h" +#include "gsm_color_button.h" + +static void cb_toggle_tree (GtkAction *action, gpointer data); + +static const GtkActionEntry menu_entries[] = +{ + // xgettext: noun, top level menu. + // "File" did not make sense for system-monitor + { "Monitor", NULL, N_("_Monitor") }, + { "Edit", NULL, N_("_Edit") }, + { "View", NULL, N_("_View") }, + { "Help", NULL, N_("_Help") }, + + { "Lsof", GTK_STOCK_FIND, N_("Search for _Open Files"), "<control>O", + N_("Search for open files"), G_CALLBACK(cb_show_lsof) }, + { "Quit", GTK_STOCK_QUIT, NULL, NULL, + N_("Quit the program"), G_CALLBACK (cb_app_exit) }, + + + { "StopProcess", NULL, N_("_Stop Process"), "<control>S", + N_("Stop process"), G_CALLBACK(cb_kill_sigstop) }, + { "ContProcess", NULL, N_("_Continue Process"), "<control>C", + N_("Continue process if stopped"), G_CALLBACK(cb_kill_sigcont) }, + + { "EndProcess", NULL, N_("_End Process"), "<control>E", + N_("Force process to finish normally"), G_CALLBACK (cb_end_process) }, + { "KillProcess", NULL, N_("_Kill Process"), "<control>K", + N_("Force process to finish immediately"), G_CALLBACK (cb_kill_process) }, + { "ChangePriority", NULL, N_("_Change Priority..."), "<control>N", + N_("Change the order of priority of process"), G_CALLBACK (cb_renice) }, + { "Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, + N_("Configure the application"), G_CALLBACK (cb_edit_preferences) }, + + { "Refresh", GTK_STOCK_REFRESH, N_("_Refresh"), "<control>R", + N_("Refresh the process list"), G_CALLBACK(cb_user_refresh) }, + + { "MemoryMaps", NULL, N_("_Memory Maps"), "<control>M", + N_("Open the memory maps associated with a process"), G_CALLBACK (cb_show_memory_maps) }, + { "OpenFiles", NULL, N_("Open _Files"), "<control>F", + N_("View the files opened by a process"), G_CALLBACK (cb_show_open_files) }, + + { "HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1", + N_("Open the manual"), G_CALLBACK (cb_help_contents) }, + { "About", GTK_STOCK_ABOUT, NULL, NULL, + N_("About this application"), G_CALLBACK (cb_about) } +}; + +static const GtkToggleActionEntry toggle_menu_entries[] = +{ + { "ShowDependencies", NULL, N_("_Dependencies"), "<control>D", + N_("Show parent/child relationship between processes"), + G_CALLBACK (cb_toggle_tree), TRUE }, +}; + + +static const GtkRadioActionEntry radio_menu_entries[] = +{ + { "ShowActiveProcesses", NULL, N_("_Active Processes"), NULL, + N_("Show active processes"), ACTIVE_PROCESSES }, + { "ShowAllProcesses", NULL, N_("A_ll Processes"), NULL, + N_("Show all processes"), ALL_PROCESSES }, + { "ShowMyProcesses", NULL, N_("M_y Processes"), NULL, + N_("Show user own process"), MY_PROCESSES } +}; + + +static const char ui_info[] = +" <menubar name=\"MenuBar\">" +" <menu name=\"MonitorMenu\" action=\"Monitor\">" +" <menuitem name=\"MonitorLsofMenu\" action=\"Lsof\" />" +" <menuitem name=\"MonitorQuitMenu\" action=\"Quit\" />" +" </menu>" +" <menu name=\"EditMenu\" action=\"Edit\">" +" <menuitem name=\"EditStopProcessMenu\" action=\"StopProcess\" />" +" <menuitem name=\"EditContProcessMenu\" action=\"ContProcess\" />" +" <separator />" +" <menuitem name=\"EditEndProcessMenu\" action=\"EndProcess\" />" +" <menuitem name=\"EditKillProcessMenu\" action=\"KillProcess\" />" +" <separator />" +" <menuitem name=\"EditChangePriorityMenu\" action=\"ChangePriority\" />" +" <separator />" +" <menuitem name=\"EditPreferencesMenu\" action=\"Preferences\" />" +" </menu>" +" <menu name=\"ViewMenu\" action=\"View\">" +" <menuitem name=\"ViewActiveProcesses\" action=\"ShowActiveProcesses\" />" +" <menuitem name=\"ViewAllProcesses\" action=\"ShowAllProcesses\" />" +" <menuitem name=\"ViewMyProcesses\" action=\"ShowMyProcesses\" />" +" <separator />" +" <menuitem name=\"ViewDependenciesMenu\" action=\"ShowDependencies\" />" +" <separator />" +" <menuitem name=\"ViewMemoryMapsMenu\" action=\"MemoryMaps\" />" +" <menuitem name=\"ViewOpenFilesMenu\" action=\"OpenFiles\" />" +" <separator />" +" <menuitem name=\"ViewRefresh\" action=\"Refresh\" />" +" </menu>" +" <menu name=\"HelpMenu\" action=\"Help\">" +" <menuitem name=\"HelpContentsMenu\" action=\"HelpContents\" />" +" <menuitem name=\"HelpAboutMenu\" action=\"About\" />" +" </menu>" +" </menubar>" +" <popup name=\"PopupMenu\" action=\"Popup\">" +" <menuitem action=\"StopProcess\" />" +" <menuitem action=\"ContProcess\" />" +" <separator />" +" <menuitem action=\"EndProcess\" />" +" <menuitem action=\"KillProcess\" />" +" <separator />" +" <menuitem action=\"ChangePriority\" />" +" <separator />" +" <menuitem action=\"MemoryMaps\" />" +" <menuitem action=\"OpenFiles\" />" +" </popup>"; + + +static GtkWidget * +create_proc_view (ProcData *procdata) +{ + GtkWidget *vbox1; + GtkWidget *hbox1; + GtkWidget *scrolled; + GtkWidget *hbox2; + char* string; + + vbox1 = gtk_vbox_new (FALSE, 18); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), 12); + + hbox1 = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, FALSE, FALSE, 0); + + string = make_loadavg_string (); + procdata->loadavg = gtk_label_new (string); + g_free (string); + gtk_box_pack_start (GTK_BOX (hbox1), procdata->loadavg, FALSE, FALSE, 0); + + + scrolled = proctable_new (procdata); + if (!scrolled) + return NULL; + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), + GTK_SHADOW_IN); + + gtk_box_pack_start (GTK_BOX (vbox1), scrolled, TRUE, TRUE, 0); + + + hbox2 = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox1), hbox2, FALSE, FALSE, 0); + + procdata->endprocessbutton = gtk_button_new_with_mnemonic (_("End _Process")); + gtk_box_pack_end (GTK_BOX (hbox2), procdata->endprocessbutton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (procdata->endprocessbutton), "clicked", + G_CALLBACK (cb_end_process_button_pressed), procdata); + + + /* create popup_menu */ + procdata->popup_menu = gtk_ui_manager_get_widget (procdata->uimanager, "/PopupMenu"); + + return vbox1; +} + + +GtkWidget * +make_title_label (const char *text) +{ + GtkWidget *label; + char *full; + + full = g_strdup_printf ("<span weight=\"bold\">%s</span>", text); + label = gtk_label_new (full); + g_free (full); + + gtk_misc_set_alignment (GTK_MISC (label), 0.0f, 0.5f); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + + return label; +} + + +static GtkWidget * +create_sys_view (ProcData *procdata) +{ + GtkWidget *vbox, *hbox; + GtkWidget *cpu_box, *mem_box, *net_box; + GtkWidget *cpu_graph_box, *mem_graph_box, *net_graph_box; + GtkWidget *label,*cpu_label, *spacer; + GtkWidget *table; + GtkWidget *color_picker; + GtkWidget *mem_legend_box, *net_legend_box; + GtkSizeGroup *sizegroup; + LoadGraph *cpu_graph, *mem_graph, *net_graph; + gint i; + + + vbox = gtk_vbox_new (FALSE, 18); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + + /* The CPU BOX */ + + cpu_box = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox), cpu_box, TRUE, TRUE, 0); + + label = make_title_label (_("CPU History")); + gtk_box_pack_start (GTK_BOX (cpu_box), label, FALSE, FALSE, 0); + + cpu_graph_box = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (cpu_box), cpu_graph_box, TRUE, TRUE, 0); + + cpu_graph = new LoadGraph(LOAD_GRAPH_CPU); + gtk_box_pack_start (GTK_BOX (cpu_graph_box), + load_graph_get_widget(cpu_graph), + TRUE, + TRUE, + 0); + + sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + hbox = gtk_hbox_new(FALSE, 0); + spacer = gtk_label_new (""); + gtk_widget_set_size_request(GTK_WIDGET(spacer), 57, -1); + gtk_box_pack_start (GTK_BOX (hbox), spacer, + FALSE, FALSE, 0); + + + gtk_box_pack_start (GTK_BOX (cpu_graph_box), hbox, + FALSE, FALSE, 0); + + /*cpu_legend_box = gtk_hbox_new(TRUE, 10); + gtk_box_pack_start (GTK_BOX (hbox), cpu_legend_box, + TRUE, TRUE, 0);*/ + + GtkWidget* cpu_table = gtk_table_new(std::min(procdata->config.num_cpus / 4, 1), + std::min(procdata->config.num_cpus, 4), + TRUE); + gtk_table_set_row_spacings(GTK_TABLE(cpu_table), 6); + gtk_table_set_col_spacings(GTK_TABLE(cpu_table), 6); + gtk_box_pack_start(GTK_BOX(hbox), cpu_table, TRUE, TRUE, 0); + + for (i=0;i<procdata->config.num_cpus; i++) { + GtkWidget *temp_hbox; + gchar *text; + + temp_hbox = gtk_hbox_new (FALSE, 0); + gtk_table_attach(GTK_TABLE(cpu_table), temp_hbox, + i % 4, i % 4 + 1, + i / 4, i / 4 + 1, + static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), + static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), + 0, 0); + //gtk_size_group_add_widget (sizegroup, temp_hbox); + /*g_signal_connect (G_OBJECT (temp_hbox), "size_request", + G_CALLBACK(size_request), &cpu_size); +*/ + 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 (GTK_BOX (temp_hbox), color_picker, FALSE, TRUE, 0); + gtk_widget_set_size_request(GTK_WIDGET(color_picker), 32, -1); + if(procdata->config.num_cpus == 1) { + text = g_strdup (_("CPU")); + } else { + text = g_strdup_printf (_("CPU%d"), i+1); + } + label = gtk_label_new (text); + gtk_box_pack_start (GTK_BOX (temp_hbox), label, FALSE, FALSE, 6); + g_free (text); + + cpu_label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (cpu_label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (temp_hbox), cpu_label, TRUE, TRUE, 0); + load_graph_get_labels(cpu_graph)->cpu[i] = cpu_label; + + } + + procdata->cpu_graph = cpu_graph; + + mem_box = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox), mem_box, TRUE, TRUE, 0); + + label = make_title_label (_("Memory and Swap History")); + gtk_box_pack_start (GTK_BOX (mem_box), label, FALSE, FALSE, 0); + + mem_graph_box = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (mem_box), mem_graph_box, TRUE, TRUE, 0); + + + mem_graph = new LoadGraph(LOAD_GRAPH_MEM); + gtk_box_pack_start (GTK_BOX (mem_graph_box), + load_graph_get_widget(mem_graph), + TRUE, + TRUE, + 0); + + hbox = gtk_hbox_new(FALSE, 0); + spacer = gtk_label_new (""); + gtk_widget_set_size_request(GTK_WIDGET(spacer), 54, -1); + gtk_box_pack_start (GTK_BOX (hbox), spacer, + FALSE, FALSE, 0); + + + gtk_box_pack_start (GTK_BOX (mem_graph_box), hbox, + FALSE, FALSE, 0); + + mem_legend_box = gtk_hbox_new(TRUE, 10); + gtk_box_pack_start (GTK_BOX (hbox), mem_legend_box, + TRUE, TRUE, 0); + + table = gtk_table_new (2, 7, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (mem_legend_box), table, + TRUE, TRUE, 0); + + 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), procdata); + gtk_table_attach (GTK_TABLE (table), color_picker, 0, 1, 0, 2, GTK_SHRINK, GTK_SHRINK, 0, 0); + + label = gtk_label_new (_("Memory")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 1, 7, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + gtk_table_attach (GTK_TABLE (table), + load_graph_get_labels(mem_graph)->memory, + 1, + 2, + 1, + 2, + GTK_FILL, + GTK_FILL, + 0, + 0); + + table = gtk_table_new (2, 7, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (mem_legend_box), table, + TRUE, TRUE, 0); + + 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), procdata); + gtk_table_attach (GTK_TABLE (table), color_picker, 0, 1, 0, 2, GTK_SHRINK, GTK_SHRINK, 0, 0); + + label = gtk_label_new (_("Swap")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 1, 7, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + gtk_table_attach (GTK_TABLE (table), + load_graph_get_labels(mem_graph)->swap, + 1, + 2, + 1, + 2, + GTK_FILL, + GTK_FILL, + 0, + 0); + + procdata->mem_graph = mem_graph; + + /* The net box */ + net_box = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox), net_box, TRUE, TRUE, 0); + + label = make_title_label (_("Network History")); + gtk_box_pack_start (GTK_BOX (net_box), label, FALSE, FALSE, 0); + + net_graph_box = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (net_box), net_graph_box, TRUE, TRUE, 0); + + net_graph = new LoadGraph(LOAD_GRAPH_NET); + gtk_box_pack_start (GTK_BOX (net_graph_box), + load_graph_get_widget(net_graph), + TRUE, + TRUE, + 0); + + hbox = gtk_hbox_new(FALSE, 0); + spacer = gtk_label_new (""); + gtk_widget_set_size_request(GTK_WIDGET(spacer), 54, -1); + gtk_box_pack_start (GTK_BOX (hbox), spacer, + FALSE, FALSE, 0); + + + gtk_box_pack_start (GTK_BOX (net_graph_box), hbox, + FALSE, FALSE, 0); + + net_legend_box = gtk_hbox_new(TRUE, 10); + gtk_box_pack_start (GTK_BOX (hbox), net_legend_box, + TRUE, TRUE, 0); + + table = gtk_table_new (2, 4, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (net_legend_box), table, + TRUE, TRUE, 0); + + color_picker = gsm_color_button_new ( + &net_graph->colors.at(0), GSMCP_TYPE_NETWORK_IN); + g_signal_connect (G_OBJECT (color_picker), "color_set", + G_CALLBACK (cb_net_in_color_changed), procdata); + gtk_table_attach (GTK_TABLE (table), color_picker, 0, 1, 0, 2, GTK_SHRINK, GTK_SHRINK, 0, 0); + + label = gtk_label_new (_("Receiving")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + gtk_misc_set_alignment (GTK_MISC (load_graph_get_labels(net_graph)->net_in), + 1.0, + 0.5); +/* + hbox = gtk_hbox_new (FALSE, 0); + g_signal_connect (G_OBJECT (hbox), "size_request", + G_CALLBACK(size_request), &net_size); + gtk_box_pack_start (GTK_BOX (hbox), + load_graph_get_labels(net_graph)->net_in, + TRUE, + TRUE, + 0); +*/ + gtk_widget_set_size_request(GTK_WIDGET(load_graph_get_labels(net_graph)->net_in), 65, -1); + gtk_table_attach (GTK_TABLE (table), load_graph_get_labels(net_graph)->net_in, 2, 3, 0, 1, + static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), GTK_FILL, 0, 0); + + label = gtk_label_new (_("Total Received")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + + gtk_misc_set_alignment (GTK_MISC (load_graph_get_labels(net_graph)->net_in_total), + 1.0, + 0.5); + gtk_table_attach (GTK_TABLE (table), + load_graph_get_labels(net_graph)->net_in_total, + 2, + 3, + 1, + 2, + GTK_FILL, + GTK_FILL, + 0, + 0); + + spacer = gtk_label_new (""); + gtk_widget_set_size_request(GTK_WIDGET(spacer), 38, -1); + gtk_table_attach (GTK_TABLE (table), spacer, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (net_legend_box), table, + TRUE, TRUE, 0); + + color_picker = gsm_color_button_new ( + &net_graph->colors.at(1), GSMCP_TYPE_NETWORK_OUT); + g_signal_connect (G_OBJECT (color_picker), "color_set", + G_CALLBACK (cb_net_out_color_changed), procdata); + gtk_table_attach (GTK_TABLE (table), color_picker, 0, 1, 0, 2, GTK_SHRINK, GTK_SHRINK, 0, 0); + + label = gtk_label_new (_("Sending")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + gtk_misc_set_alignment (GTK_MISC (load_graph_get_labels(net_graph)->net_out), + 1.0, + 0.5); +/* + hbox = gtk_hbox_new (FALSE, 0); + g_signal_connect (G_OBJECT (load_graph_get_labels(net_graph)->net_out), "size_request", + G_CALLBACK(size_request), &net_size); + + gtk_box_pack_start (GTK_BOX (hbox), + load_graph_get_labels(net_graph)->net_out, + TRUE, + TRUE, + 0); +*/ + gtk_widget_set_size_request(GTK_WIDGET(load_graph_get_labels(net_graph)->net_out), 65, -1); + gtk_table_attach (GTK_TABLE (table), load_graph_get_labels(net_graph)->net_out, 2, 3, 0, 1, + static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), GTK_FILL, 0, 0); + + label = gtk_label_new (_("Total Sent")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + + gtk_misc_set_alignment (GTK_MISC (load_graph_get_labels(net_graph)->net_out_total), + 1.0, + 0.5); + gtk_table_attach (GTK_TABLE (table), + load_graph_get_labels(net_graph)->net_out_total, + 2, + 3, + 1, + 2, + GTK_FILL, + GTK_FILL, + 0, + 0); + + spacer = gtk_label_new (""); + gtk_widget_set_size_request(GTK_WIDGET(spacer), 38, -1); + gtk_table_attach (GTK_TABLE (table), spacer, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + procdata->net_graph = net_graph; + + return vbox; +} + +static void +menu_item_select_cb (GtkMenuItem *proxy, + ProcData *procdata) +{ + GtkAction *action; + char *message; + + action = gtk_activatable_get_related_action (GTK_ACTIVATABLE(proxy)); + g_assert(action); + + g_object_get (G_OBJECT (action), "tooltip", &message, NULL); + if (message) + { + gtk_statusbar_push (GTK_STATUSBAR (procdata->statusbar), + procdata->tip_message_cid, message); + g_free (message); + } +} + +static void +menu_item_deselect_cb (GtkMenuItem *proxy, + ProcData *procdata) +{ + gtk_statusbar_pop (GTK_STATUSBAR (procdata->statusbar), + procdata->tip_message_cid); +} + +static void +connect_proxy_cb (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy, + ProcData *procdata) +{ + if (GTK_IS_MENU_ITEM (proxy)) { + g_signal_connect (proxy, "select", + G_CALLBACK (menu_item_select_cb), procdata); + g_signal_connect (proxy, "deselect", + G_CALLBACK (menu_item_deselect_cb), procdata); + } +} + +static void +disconnect_proxy_cb (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy, + ProcData *procdata) +{ + if (GTK_IS_MENU_ITEM (proxy)) { + g_signal_handlers_disconnect_by_func + (proxy, (void*)(G_CALLBACK(menu_item_select_cb)), procdata); + g_signal_handlers_disconnect_by_func + (proxy, (void*)(G_CALLBACK(menu_item_deselect_cb)), procdata); + } +} + +void +create_main_window (ProcData *procdata) +{ + gint width, height; + GtkWidget *app; + GtkAction *action; + GtkWidget *menubar; + GtkWidget *main_box; + GtkWidget *notebook; + GtkWidget *tab_label1, *tab_label2, *tab_label3; + GtkWidget *vbox1; + GtkWidget *sys_box, *devices_box; + GtkWidget *sysinfo_box, *sysinfo_label; + + app = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(app), _("System Monitor")); + + GdkScreen* screen = gtk_widget_get_screen(app); + GdkColormap* colormap = gdk_screen_get_rgba_colormap(screen); + + /* use rgba colormap, if available */ + if (colormap) + gtk_widget_set_default_colormap(colormap); + + main_box = gtk_vbox_new (FALSE, 0); + gtk_container_add(GTK_CONTAINER(app), main_box); + + width = procdata->config.width; + height = procdata->config.height; + gtk_window_set_default_size (GTK_WINDOW (app), width, height); + gtk_window_set_resizable (GTK_WINDOW (app), TRUE); + + /* create the menubar */ + procdata->uimanager = gtk_ui_manager_new (); + + /* show tooltips in the statusbar */ + g_signal_connect (procdata->uimanager, "connect_proxy", + G_CALLBACK (connect_proxy_cb), procdata); + g_signal_connect (procdata->uimanager, "disconnect_proxy", + G_CALLBACK (disconnect_proxy_cb), procdata); + + gtk_window_add_accel_group (GTK_WINDOW (app), + gtk_ui_manager_get_accel_group (procdata->uimanager)); + + if (!gtk_ui_manager_add_ui_from_string (procdata->uimanager, + ui_info, + -1, + NULL)) { + g_error("building menus failed"); + } + + procdata->action_group = gtk_action_group_new ("ProcmanActions"); + gtk_action_group_set_translation_domain (procdata->action_group, NULL); + gtk_action_group_add_actions (procdata->action_group, + menu_entries, + G_N_ELEMENTS (menu_entries), + procdata); + gtk_action_group_add_toggle_actions (procdata->action_group, + toggle_menu_entries, + G_N_ELEMENTS (toggle_menu_entries), + procdata); + + gtk_action_group_add_radio_actions (procdata->action_group, + radio_menu_entries, + G_N_ELEMENTS (radio_menu_entries), + procdata->config.whose_process, + G_CALLBACK(cb_radio_processes), + procdata); + + gtk_ui_manager_insert_action_group (procdata->uimanager, + procdata->action_group, + 0); + + menubar = gtk_ui_manager_get_widget (procdata->uimanager, "/MenuBar"); + gtk_box_pack_start (GTK_BOX (main_box), menubar, FALSE, FALSE, 0); + + + /* create the main notebook */ + procdata->notebook = notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (main_box), + notebook, + TRUE, + TRUE, + 0); + + sysinfo_box = gtk_hbox_new(TRUE, 0); // procman_create_sysinfo_view(); + sysinfo_label = gtk_label_new(_("System")); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), sysinfo_box, sysinfo_label); + + vbox1 = create_proc_view (procdata); + tab_label1 = gtk_label_new (_("Processes")); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox1, tab_label1); + + sys_box = create_sys_view (procdata); + tab_label2 = gtk_label_new (_("Resources")); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), sys_box, tab_label2); + + devices_box = create_disk_view (procdata); + tab_label3 = gtk_label_new (_("File Systems")); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), devices_box, tab_label3); + + g_signal_connect (G_OBJECT (notebook), "switch-page", + G_CALLBACK (cb_switch_page), procdata); + g_signal_connect (G_OBJECT (notebook), "change-current-page", + G_CALLBACK (cb_change_current_page), procdata); + + gtk_widget_show_all(notebook); // need to make page switch work + gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), procdata->config.current_tab); + cb_change_current_page (GTK_NOTEBOOK (notebook), procdata->config.current_tab, procdata); + g_signal_connect (G_OBJECT (app), "delete_event", + G_CALLBACK (cb_app_delete), + procdata); + + + /* create the statusbar */ + procdata->statusbar = gtk_statusbar_new(); + gtk_box_pack_end(GTK_BOX(main_box), procdata->statusbar, FALSE, FALSE, 0); + procdata->tip_message_cid = gtk_statusbar_get_context_id + (GTK_STATUSBAR (procdata->statusbar), "tip_message"); + + + action = gtk_action_group_get_action (procdata->action_group, "ShowDependencies"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + procdata->config.show_tree); + + gtk_widget_show_all(app); + procdata->app = app; +} + +void +do_popup_menu (ProcData *procdata, GdkEventButton *event) +{ + guint button; + guint32 event_time; + + if (event) { + button = event->button; + event_time = event->time; + } + else { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (procdata->popup_menu), NULL, NULL, + NULL, NULL, button, event_time); +} + +void +update_sensitivity(ProcData *data) +{ + const char * const selected_actions[] = { "StopProcess", + "ContProcess", + "EndProcess", + "KillProcess", + "ChangePriority", + "MemoryMaps", + "OpenFiles" }; + + const char * const processes_actions[] = { "ShowActiveProcesses", + "ShowAllProcesses", + "ShowMyProcesses", + "ShowDependencies", + "Refresh" + }; + + size_t i; + gboolean processes_sensitivity, selected_sensitivity; + GtkAction *action; + + processes_sensitivity = (data->config.current_tab == PROCMAN_TAB_PROCESSES); + selected_sensitivity = (processes_sensitivity && data->selected_process != NULL); + + if(data->endprocessbutton) { + /* avoid error on startup if endprocessbutton + has not been built yet */ + gtk_widget_set_sensitive(data->endprocessbutton, selected_sensitivity); + } + + for (i = 0; i != G_N_ELEMENTS(processes_actions); ++i) { + action = gtk_action_group_get_action(data->action_group, + processes_actions[i]); + gtk_action_set_sensitive(action, processes_sensitivity); + } + + for (i = 0; i != G_N_ELEMENTS(selected_actions); ++i) { + action = gtk_action_group_get_action(data->action_group, + selected_actions[i]); + gtk_action_set_sensitive(action, selected_sensitivity); + } +} + +static void +cb_toggle_tree (GtkAction *action, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfClient *client = procdata->client; + gboolean show; + + show = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + if (show == procdata->config.show_tree) + return; + + mateconf_client_set_bool (client, "/apps/procman/show_tree", show, NULL); +} diff --git a/src/interface.h b/src/interface.h new file mode 100644 index 0000000..4bda35a --- /dev/null +++ b/src/interface.h @@ -0,0 +1,32 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _PROCMAN_INTERFACE_H_ +#define _PROCMAN_INTERFACE_H_ + +#include <glib/gtypes.h> +#include <gtk/gtk.h> +#include "procman.h" + +void create_main_window (ProcData *data); +void update_sensitivity (ProcData *data); +void do_popup_menu(ProcData *data, GdkEventButton *event); +GtkWidget * make_title_label (const char *text); + +#endif /* _PROCMAN_INTERFACE_H_ */ diff --git a/src/load-graph.cpp b/src/load-graph.cpp new file mode 100644 index 0000000..79700d4 --- /dev/null +++ b/src/load-graph.cpp @@ -0,0 +1,787 @@ +#include <config.h> + +#include <gdkmm/pixbuf.h> + +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <signal.h> +#include <dirent.h> +#include <string.h> +#include <time.h> +#include <gdk/gdkx.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 <math.h> + +#include <algorithm> + +#include "procman.h" +#include "load-graph.h" +#include "util.h" +#include "gsm_color_button.h" + + +void LoadGraph::clear_background() +{ + if (this->background) { + g_object_unref(this->background); + this->background = NULL; + } +} + + +unsigned LoadGraph::num_bars() const +{ + unsigned n; + + // keep 100 % num_bars == 0 + switch (static_cast<int>(this->draw_height / (this->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; +} + + + +#define FRAME_WIDTH 4 +void draw_background(LoadGraph *g) { + GtkAllocation allocation; + double dash[2] = { 1.0, 2.0 }; + cairo_t *cr; + guint i; + unsigned num_bars; + char *caption; + cairo_text_extents_t extents; + + num_bars = g->num_bars(); + g->graph_dely = (g->draw_height - 15) / num_bars; /* round to int to avoid AA blur */ + g->real_draw_height = g->graph_dely * num_bars; + g->graph_delx = (g->draw_width - 2.0 - g->rmargin - g->indent) / (LoadGraph::NUM_POINTS - 3); + g->graph_buffer_offset = (int) (1.5 * g->graph_delx) + FRAME_WIDTH ; + + gtk_widget_get_allocation (g->disp, &allocation); + g->background = gdk_pixmap_new (GDK_DRAWABLE (gtk_widget_get_window (g->disp)), + allocation.width, + allocation.height, + -1); + cr = gdk_cairo_create (g->background); + + // set the background colour + GtkStyle *style = gtk_widget_get_style (ProcData::get_instance()->notebook); + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]); + cairo_paint (cr); + + /* draw frame */ + cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH); + + /* Draw background rectangle */ + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + cairo_rectangle (cr, g->rmargin + g->indent, 0, + g->draw_width - g->rmargin - g->indent, g->real_draw_height); + cairo_fill(cr); + + cairo_set_line_width (cr, 1.0); + cairo_set_dash (cr, dash, 2, 0); + cairo_set_font_size (cr, g->fontsize); + + for (i = 0; i <= num_bars; ++i) { + double y; + + if (i == 0) + y = 0.5 + g->fontsize / 2.0; + else if (i == num_bars) + y = i * g->graph_dely + 0.5; + else + y = i * g->graph_dely + g->fontsize / 2.0; + + gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); + if (g->type == LOAD_GRAPH_NET) { + // operation orders matters so it's 0 if i == num_bars + unsigned rate = g->net.max - (i * g->net.max / num_bars); + const std::string caption(procman::format_network_rate(rate, g->net.max)); + cairo_text_extents (cr, caption.c_str(), &extents); + cairo_move_to (cr, g->indent - extents.width + 20, y); + cairo_show_text (cr, caption.c_str()); + } else { + // operation orders matters so it's 0 if i == num_bars + caption = g_strdup_printf("%d %%", 100 - i * (100 / num_bars)); + cairo_text_extents (cr, caption, &extents); + cairo_move_to (cr, g->indent - extents.width + 20, y); + cairo_show_text (cr, caption); + g_free (caption); + } + + cairo_set_source_rgba (cr, 0, 0, 0, 0.75); + cairo_move_to (cr, g->rmargin + g->indent - 3, i * g->graph_dely + 0.5); + cairo_line_to (cr, g->draw_width - 0.5, i * g->graph_dely + 0.5); + } + cairo_stroke (cr); + + cairo_set_dash (cr, dash, 2, 1.5); + + const unsigned total_seconds = g->speed * (LoadGraph::NUM_POINTS - 2) / 1000; + + for (unsigned int i = 0; i < 7; i++) { + double x = (i) * (g->draw_width - g->rmargin - g->indent) / 6; + cairo_set_source_rgba (cr, 0, 0, 0, 0.75); + cairo_move_to (cr, (ceil(x) + 0.5) + g->rmargin + g->indent, 0.5); + cairo_line_to (cr, (ceil(x) + 0.5) + g->rmargin + g->indent, g->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); + cairo_text_extents (cr, caption, &extents); + cairo_move_to (cr, ((ceil(x) + 0.5) + g->rmargin + g->indent) - (extents.width/2), g->draw_height); + gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); + cairo_show_text (cr, caption); + g_free (caption); + } + + cairo_stroke (cr); + cairo_destroy (cr); +} + +/* Redraws the backing buffer for the load graph and updates the window */ +void +load_graph_draw (LoadGraph *g) +{ + /* repaint */ + gtk_widget_queue_draw (g->disp); +} + +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 g = static_cast<LoadGraph*>(data_ptr); + + gtk_widget_get_allocation (widget, &allocation); + g->draw_width = allocation.width - 2 * FRAME_WIDTH; + g->draw_height = allocation.height - 2 * FRAME_WIDTH; + + g->clear_background(); + + if (g->gc == NULL) { + g->gc = gdk_gc_new (GDK_DRAWABLE (gtk_widget_get_window (widget))); + } + + load_graph_draw (g); + + return TRUE; +} + +static gboolean +load_graph_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer data_ptr) +{ + LoadGraph * const g = static_cast<LoadGraph*>(data_ptr); + GtkAllocation allocation; + GdkWindow *window; + + guint i, j; + gdouble sample_width, x_offset; + + if (g->background == NULL) { + draw_background(g); + } + + window = gtk_widget_get_window (g->disp); + gtk_widget_get_allocation (g->disp, &allocation); + gdk_draw_drawable (window, + g->gc, + g->background, + 0, 0, 0, 0, + allocation.width, + allocation.height); + + /* Number of pixels wide for one graph point */ + sample_width = (float)(g->draw_width - g->rmargin - g->indent) / (float)LoadGraph::NUM_POINTS; + /* General offset */ + x_offset = g->draw_width - g->rmargin + (sample_width*2); + + /* Subframe offset */ + x_offset += g->rmargin - ((sample_width / g->frames_per_unit) * g->render_counter); + + /* draw the graph */ + cairo_t* cr; + + cr = gdk_cairo_create (window); + + 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, g->rmargin + g->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1, + g->draw_width - g->rmargin - g->indent - 1, g->real_draw_height + FRAME_WIDTH - 1); + cairo_clip(cr); + + for (j = 0; j < g->n; ++j) { + cairo_move_to (cr, x_offset, (1.0f - g->data[0][j]) * g->real_draw_height); + gdk_cairo_set_source_color (cr, &(g->colors [j])); + + for (i = 1; i < LoadGraph::NUM_POINTS; ++i) { + if (g->data[i][j] == -1.0f) + continue; + cairo_curve_to (cr, + x_offset - ((i - 0.5f) * g->graph_delx), + (1.0f - g->data[i-1][j]) * g->real_draw_height + 3.5f, + x_offset - ((i - 0.5f) * g->graph_delx), + (1.0f - g->data[i][j]) * g->real_draw_height + 3.5f, + x_offset - (i * g->graph_delx), + (1.0f - g->data[i][j]) * g->real_draw_height + 3.5f); + } + cairo_stroke (cr); + + } + + cairo_destroy (cr); + + return TRUE; +} + +static void +get_load (LoadGraph *g) +{ + guint i; + glibtop_cpu cpu; + + glibtop_get_cpu (&cpu); + +#undef NOW +#undef LAST +#define NOW (g->cpu.times[g->cpu.now]) +#define LAST (g->cpu.times[g->cpu.now ^ 1]) + + if (g->n == 1) { + NOW[0][CPU_TOTAL] = cpu.total; + NOW[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys; + } else { + for (i = 0; i < g->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 + + for (i = 0; i < g->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); + g->data[0][i] = load; + + /* Update label */ + text = g_strdup_printf("%.1f%%", load * 100.0f); + gtk_label_set_text(GTK_LABEL(g->labels.cpu[i]), text); + g_free(text); + } + + g->cpu.now ^= 1; + +#undef NOW +#undef LAST +} + + +namespace +{ + + void set_memory_label_and_picker(GtkLabel* label, GSMColorButton* picker, + guint64 used, guint64 total, double percent) + { + char* used_text; + char* total_text; + char* text; + + used_text = procman::format_size(used); + total_text = procman::format_size(total); + // xgettext: 540MiB (53 %) of 1.0 GiB + text = g_strdup_printf(_("%s (%.1f %%) of %s"), used_text, 100.0 * percent, total_text); + gtk_label_set_text(label, text); + g_free(used_text); + g_free(total_text); + g_free(text); + + if (picker) + gsm_color_button_set_fraction(picker, percent); + } +} + +static void +get_memory (LoadGraph *g) +{ + 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(g->labels.memory), + GSM_COLOR_BUTTON(g->mem_color_picker), + mem.user, mem.total, mempercent); + + set_memory_label_and_picker(GTK_LABEL(g->labels.swap), + GSM_COLOR_BUTTON(g->swap_color_picker), + swap.used, swap.total, swappercent); + + g->data[0][0] = mempercent; + g->data[0][1] = swappercent; +} + +static void +net_scale (LoadGraph *g, unsigned din, unsigned dout) +{ + g->data[0][0] = 1.0f * din / g->net.max; + g->data[0][1] = 1.0f * dout / g->net.max; + + unsigned dmax = std::max(din, dout); + g->net.values[g->net.cur] = dmax; + g->net.cur = (g->net.cur + 1) % LoadGraph::NUM_POINTS; + + unsigned new_max; + // both way, new_max is the greatest value + if (dmax >= g->net.max) + new_max = dmax; + else + new_max = *std::max_element(&g->net.values[0], + &g->net.values[LoadGraph::NUM_POINTS]); + + // + // Round network maximum + // + + const unsigned bak_max(new_max); + + if (ProcData::get_instance()->config.network_in_bits) { + // TODO: fix logic to give a nice scale with bits + + // 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 125 bytes == 1kbit + new_max = std::max(new_max, 125U); + + } 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, 1024U); + + // 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 + + unsigned pow2 = std::floor(log2(new_max)); + unsigned base10 = pow2 / 10; + unsigned coef10 = std::ceil(new_max / double(1UL << (base10 * 10))); + g_assert(new_max <= (coef10 * (1UL << (base10 * 10)))); + + // then decompose coef10 = x * 10**factor10 + // where factor10 is integer and x < 10 + // so we new_max has only 1 significant digit + + unsigned factor10 = std::pow(10.0, std::floor(std::log10(coef10))); + coef10 = std::ceil(coef10 / double(factor10)) * factor10; + + // then make coef10 divisible by num_bars + if (coef10 % g->num_bars() != 0) + coef10 = coef10 + (g->num_bars() - coef10 % g->num_bars()); + g_assert(coef10 % g->num_bars() == 0); + + new_max = coef10 * (1UL << (base10 * 10)); + procman_debug("bak %u new_max %u pow2 %u coef10 %u", bak_max, new_max, pow2, coef10); + } + + if (bak_max > new_max) { + procman_debug("overflow detected: bak=%u new=%u", 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 * g->net.max) < new_max && new_max <= g->net.max) + return; + + const float scale = 1.0f * g->net.max / new_max; + + for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) { + if (g->data[i][0] >= 0.0f) { + g->data[i][0] *= scale; + g->data[i][1] *= scale; + } + } + + procman_debug("rescale dmax = %u max = %u new_max = %u", dmax, g->net.max, new_max); + + g->net.max = new_max; + + // force the graph background to be redrawn now that scale has changed + g->clear_background(); +} + +static void +get_net (LoadGraph *g) +{ + glibtop_netlist netlist; + char **ifnames; + guint32 i; + guint64 in = 0, out = 0; + GTimeVal time; + unsigned din, dout; + + ifnames = glibtop_get_netlist(&netlist); + + for (i = 0; i < netlist.number; ++i) + { + glibtop_netload netload; + glibtop_get_netload (&netload, ifnames[i]); + + if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK)) + continue; + + /* Skip interfaces without any IPv4/IPv6 address (or + those with only a LINK ipv6 addr) However we need to + be able to exclude these while still keeping the + value so when they get online (with NetworkManager + for example) we don't get a 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); + + g_get_current_time (&time); + + if (in >= g->net.last_in && out >= g->net.last_out && + g->net.time.tv_sec != 0) { + float dtime; + dtime = time.tv_sec - g->net.time.tv_sec + + (float) (time.tv_usec - g->net.time.tv_usec) / G_USEC_PER_SEC; + din = static_cast<unsigned>((in - g->net.last_in) / dtime); + dout = static_cast<unsigned>((out - g->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; + } + + g->net.last_in = in; + g->net.last_out = out; + g->net.time = time; + + net_scale(g, din, dout); + + + gtk_label_set_text (GTK_LABEL (g->labels.net_in), procman::format_network_rate(din).c_str()); + gtk_label_set_text (GTK_LABEL (g->labels.net_in_total), procman::format_network(in).c_str()); + + gtk_label_set_text (GTK_LABEL (g->labels.net_out), procman::format_network_rate(dout).c_str()); + gtk_label_set_text (GTK_LABEL (g->labels.net_out_total), procman::format_network(out).c_str()); +} + + +/* Updates the load graph when the timeout expires */ +static gboolean +load_graph_update (gpointer user_data) +{ + LoadGraph * const g = static_cast<LoadGraph*>(user_data); + + if (g->render_counter == g->frames_per_unit - 1) { + std::rotate(&g->data[0], &g->data[LoadGraph::NUM_POINTS - 1], &g->data[LoadGraph::NUM_POINTS]); + + switch (g->type) { + case LOAD_GRAPH_CPU: + get_load(g); + break; + case LOAD_GRAPH_MEM: + get_memory(g); + break; + case LOAD_GRAPH_NET: + get_net(g); + break; + default: + g_assert_not_reached(); + } + } + + if (g->draw) + load_graph_draw (g); + + g->render_counter++; + + if (g->render_counter >= g->frames_per_unit) + g->render_counter = 0; + + return TRUE; +} + + + +LoadGraph::~LoadGraph() +{ + load_graph_stop(this); + + if (this->timer_index) + g_source_remove(this->timer_index); + + this->clear_background(); +} + + + +static gboolean +load_graph_destroy (GtkWidget *widget, gpointer data_ptr) +{ + LoadGraph * const g = static_cast<LoadGraph*>(data_ptr); + + delete g; + + return FALSE; +} + + +LoadGraph::LoadGraph(guint type) + : fontsize(0.0), + rmargin(0.0), + indent(0.0), + n(0), + type(0), + speed(0), + draw_width(0), + draw_height(0), + render_counter(0), + frames_per_unit(0), + graph_dely(0), + real_draw_height(0), + graph_delx(0.0), + graph_buffer_offset(0), + main_widget(NULL), + disp(NULL), + gc(NULL), + background(NULL), + timer_index(0), + draw(FALSE), + mem_color_picker(NULL), + swap_color_picker(NULL) +{ + LoadGraph * const g = this; + + // FIXME: + // on configure, g->frames_per_unit = g->draw_width/(LoadGraph::NUM_POINTS); + // knock FRAMES down to 5 until cairo gets faster + g->frames_per_unit = 10; // this will be changed but needs initialising + g->fontsize = 8.0; + g->rmargin = 3.5 * g->fontsize; + g->indent = 24.0; + + g->type = type; + switch (type) { + case LOAD_GRAPH_CPU: + memset(&this->cpu, 0, sizeof g->cpu); + g->n = ProcData::get_instance()->config.num_cpus; + + for(guint i = 0; i < G_N_ELEMENTS(g->labels.cpu); ++i) + g->labels.cpu[i] = gtk_label_new(NULL); + + break; + + case LOAD_GRAPH_MEM: + g->n = 2; + g->labels.memory = gtk_label_new(NULL); + g->labels.swap = gtk_label_new(NULL); + break; + + case LOAD_GRAPH_NET: + memset(&this->net, 0, sizeof g->net); + g->n = 2; + g->net.max = 1; + g->labels.net_in = gtk_label_new(NULL); + g->labels.net_in_total = gtk_label_new(NULL); + g->labels.net_out = gtk_label_new(NULL); + g->labels.net_out_total = gtk_label_new(NULL); + break; + } + + g->speed = ProcData::get_instance()->config.graph_update_interval; + + g->colors.resize(g->n); + + switch (type) { + case LOAD_GRAPH_CPU: + memcpy(&g->colors[0], ProcData::get_instance()->config.cpu_color, + g->n * sizeof g->colors[0]); + break; + case LOAD_GRAPH_MEM: + g->colors[0] = ProcData::get_instance()->config.mem_color; + g->colors[1] = ProcData::get_instance()->config.swap_color; + g->mem_color_picker = gsm_color_button_new (&g->colors[0], + GSMCP_TYPE_PIE); + g->swap_color_picker = gsm_color_button_new (&g->colors[1], + GSMCP_TYPE_PIE); + break; + case LOAD_GRAPH_NET: + g->colors[0] = ProcData::get_instance()->config.net_in_color; + g->colors[1] = ProcData::get_instance()->config.net_out_color; + break; + } + + g->timer_index = 0; + g->render_counter = (g->frames_per_unit - 1); + g->draw = FALSE; + + g->main_widget = gtk_vbox_new (FALSE, FALSE); + gtk_widget_set_size_request(g->main_widget, -1, LoadGraph::GRAPH_MIN_HEIGHT); + gtk_widget_show (g->main_widget); + + g->disp = gtk_drawing_area_new (); + gtk_widget_show (g->disp); + g_signal_connect (G_OBJECT (g->disp), "expose_event", + G_CALLBACK (load_graph_expose), g); + g_signal_connect (G_OBJECT(g->disp), "configure_event", + G_CALLBACK (load_graph_configure), g); + g_signal_connect (G_OBJECT(g->disp), "destroy", + G_CALLBACK (load_graph_destroy), g); + + gtk_widget_set_events (g->disp, GDK_EXPOSURE_MASK); + + gtk_box_pack_start (GTK_BOX (g->main_widget), g->disp, TRUE, TRUE, 0); + + + /* Allocate data in a contiguous block */ + g->data_block = std::vector<float>(g->n * LoadGraph::NUM_POINTS, -1.0f); + + for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i) + g->data[i] = &g->data_block[0] + i * g->n; + + gtk_widget_show_all (g->main_widget); +} + +void +load_graph_start (LoadGraph *g) +{ + if(!g->timer_index) { + + load_graph_update(g); + + g->timer_index = g_timeout_add (g->speed / g->frames_per_unit, + load_graph_update, + g); + } + + g->draw = TRUE; +} + +void +load_graph_stop (LoadGraph *g) +{ + /* don't draw anymore, but continue to poll */ + g->draw = FALSE; +} + +void +load_graph_change_speed (LoadGraph *g, + guint new_speed) +{ + if (g->speed == new_speed) + return; + + g->speed = new_speed; + + g_assert(g->timer_index); + + if(g->timer_index) { + g_source_remove (g->timer_index); + g->timer_index = g_timeout_add (g->speed / g->frames_per_unit, + load_graph_update, + g); + } + + g->clear_background(); +} + + +LoadGraphLabels* +load_graph_get_labels (LoadGraph *g) +{ + return &g->labels; +} + +GtkWidget* +load_graph_get_widget (LoadGraph *g) +{ + return g->main_widget; +} + +GtkWidget* +load_graph_get_mem_color_picker(LoadGraph *g) +{ + return g->mem_color_picker; +} + +GtkWidget* +load_graph_get_swap_color_picker(LoadGraph *g) +{ + return g->swap_color_picker; +} diff --git a/src/load-graph.h b/src/load-graph.h new file mode 100644 index 0000000..6111c78 --- /dev/null +++ b/src/load-graph.h @@ -0,0 +1,131 @@ +#ifndef _PROCMAN_LOAD_GRAPH_H_ +#define _PROCMAN_LOAD_GRAPH_H_ + +#include <glib/gtypes.h> +#include <glibtop/cpu.h> + +enum +{ + LOAD_GRAPH_CPU, + LOAD_GRAPH_MEM, + LOAD_GRAPH_NET +}; + + +enum { + CPU_TOTAL, + CPU_USED, + N_CPU_STATES +}; + + +struct LoadGraphLabels +{ + GtkWidget *cpu[GLIBTOP_NCPU]; + GtkWidget *memory; + GtkWidget *swap; + GtkWidget *net_in; + GtkWidget *net_in_total; + GtkWidget *net_out; + GtkWidget *net_out_total; +}; + + + +struct LoadGraph { + + 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; + 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<GdkColor> colors; + + std::vector<float> data_block; + gfloat* data[NUM_POINTS]; + + GtkWidget *main_widget; + GtkWidget *disp; + + GdkGC *gc; + GdkDrawable *background; + + guint timer_index; + + gboolean draw; + + LoadGraphLabels labels; + GtkWidget *mem_color_picker; + GtkWidget *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; + GTimeVal time; + unsigned int max; + unsigned values[NUM_POINTS]; + size_t cur; + } net; + /* }; */ +}; + + + +/* Force a drawing update */ +void +load_graph_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); + +LoadGraphLabels* +load_graph_get_labels (LoadGraph *g) G_GNUC_CONST; + + +GtkWidget* +load_graph_get_widget (LoadGraph *g) G_GNUC_CONST; + +GtkWidget* +load_graph_get_mem_color_picker(LoadGraph *g) G_GNUC_CONST; + +GtkWidget* +load_graph_get_swap_color_picker(LoadGraph *g) G_GNUC_CONST; + + +#endif /* _PROCMAN_LOAD_GRAPH_H_ */ diff --git a/src/lsof.cpp b/src/lsof.cpp new file mode 100644 index 0000000..24f14a5 --- /dev/null +++ b/src/lsof.cpp @@ -0,0 +1,408 @@ +#include <config.h> + +#include <gtkmm/messagedialog.h> +#include <glib/gi18n.h> +#include <glibtop/procopenfiles.h> + +#include <sys/wait.h> + +// #include <libsexy/sexy-icon-entry.h> + + +#include <set> +#include <string> +#include <sstream> +#include <iterator> + +#include "regex.h" + +#include "procman.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 { + + GtkListStore *model; + GtkEntry *entry; + GtkWindow *window; + GtkLabel *count; + ProcData *procdata; + bool case_insensitive; + + + GUI() + { + procman_debug("New Lsof GUI %p", this); + } + + + ~GUI() + { + procman_debug("Destroying Lsof GUI %p", this); + } + + + void clear_results() + { + gtk_list_store_clear(this->model); + gtk_label_set_text(this->count, ""); + } + + + void clear() + { + this->clear_results(); + gtk_entry_set_text(this->entry, ""); + } + + + void display_regex_error(const Glib::RegexError& error) + { + const char * msg = _("<b>Error</b>\n" + "'%s' is not a valid Perl regular expression.\n" + "%s"); + std::string message = make_string(g_strdup_printf(msg, this->pattern().c_str(), error.what().c_str())); + + Gtk::MessageDialog dialog(message, + true, // use markup + Gtk::MESSAGE_ERROR, + Gtk::BUTTONS_OK, + true); // modal + dialog.run(); + } + + + void update_count(unsigned count) + { + string s = static_cast<std::ostringstream&>(std::ostringstream() << count).str(); + gtk_label_set_text(this->count, s.c_str()); + } + + + string pattern() const + { + return gtk_entry_get_text(this->entry); + } + + + void search() + { + typedef std::set<string> MatchSet; + typedef MatchSet::const_iterator iterator; + + this->clear_results(); + + + try { + Lsof lsof(this->pattern(), this->case_insensitive); + + unsigned count = 0; + + for (ProcInfo::Iterator it(ProcInfo::begin()); it != ProcInfo::end(); ++it) { + const ProcInfo &info(*it->second); + + MatchSet matches; + lsof.search(info, std::inserter(matches, matches.begin())); + count += matches.size(); + + for (iterator it(matches.begin()), end(matches.end()); it != end; ++it) { + 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, + PROCMAN_LSOF_COL_PID, info.pid, + PROCMAN_LSOF_COL_FILENAME, it->c_str(), + -1); + } + } + + this->update_count(count); + } + catch (Glib::RegexError& error) { + this->display_regex_error(error); + } + } + + + static void search_button_clicked(GtkButton *, gpointer data) + { + static_cast<GUI*>(data)->search(); + } + + + static void search_entry_activate(GtkEntry *, gpointer data) + { + static_cast<GUI*>(data)->search(); + } + + + static void clear_button_clicked(GtkButton *, gpointer data) + { + static_cast<GUI*>(data)->clear(); + } + + + 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(ProcData *procdata) +{ + 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 + ); + + GtkWidget *tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + g_object_unref(model); + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE); + + + 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); + + + GtkWidget *dialog; /* = gtk_dialog_new_with_buttons(_("Search for Open Files"), NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); */ + dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(procdata->app)); + gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE); + // gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_title(GTK_WINDOW(dialog), _("Search for Open Files")); + + // g_signal_connect(G_OBJECT(dialog), "response", + // G_CALLBACK(close_dialog), NULL); + gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE); + gtk_window_set_default_size(GTK_WINDOW(dialog), 575, 400); + // gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(dialog), 12); + GtkWidget *mainbox = gtk_vbox_new(FALSE, 12); + gtk_container_add(GTK_CONTAINER(dialog), mainbox); + gtk_box_set_spacing(GTK_BOX(mainbox), 6); + + + // Label, entry and search button + + GtkWidget *hbox1 = gtk_hbox_new(FALSE, 12); + gtk_box_pack_start(GTK_BOX(mainbox), hbox1, FALSE, FALSE, 0); + + GtkWidget *image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_DIALOG); + gtk_box_pack_start(GTK_BOX(hbox1), image, FALSE, FALSE, 0); + + + GtkWidget *vbox2 = gtk_vbox_new(FALSE, 12); + gtk_box_pack_start(GTK_BOX(hbox1), vbox2, TRUE, TRUE, 0); + + + GtkWidget *hbox = gtk_hbox_new(FALSE, 12); + gtk_box_pack_start(GTK_BOX(vbox2), hbox, TRUE, TRUE, 0); + GtkWidget *label = gtk_label_new_with_mnemonic(_("_Name contains:")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + GtkWidget *entry = gtk_entry_new(); + + // entry = sexy_icon_entry_new(); + // sexy_icon_entry_add_clear_button(SEXY_ICON_ENTRY(entry)); + // GtkWidget *icon = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_MENU); + // sexy_icon_entry_set_icon(SEXY_ICON_ENTRY(entry), SEXY_ICON_ENTRY_PRIMARY, GTK_IMAGE(icon)); + + gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0); + GtkWidget *search_button = gtk_button_new_from_stock(GTK_STOCK_FIND); + gtk_box_pack_start(GTK_BOX(hbox), search_button, FALSE, FALSE, 0); + GtkWidget *clear_button = gtk_button_new_from_stock(GTK_STOCK_CLEAR); + gtk_box_pack_start(GTK_BOX(hbox), clear_button, FALSE, FALSE, 0); + + + GtkWidget *case_button = gtk_check_button_new_with_mnemonic(_("Case insensitive matching")); + GtkWidget *hbox3 = gtk_hbox_new(FALSE, 12); + gtk_box_pack_start(GTK_BOX(hbox3), case_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox2), hbox3, FALSE, FALSE, 0); + + + GtkWidget *results_box = gtk_hbox_new(FALSE, 12); + gtk_box_pack_start(GTK_BOX(mainbox), results_box, FALSE, FALSE, 0); + GtkWidget *results_label = gtk_label_new_with_mnemonic(_("S_earch results:")); + gtk_box_pack_start(GTK_BOX(results_box), results_label, FALSE, FALSE, 0); + GtkWidget *count_label = gtk_label_new(NULL); + gtk_box_pack_end(GTK_BOX(results_box), count_label, FALSE, FALSE, 0); + + + + + // Scrolled TreeView + GtkWidget *scrolled = 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), tree); + gtk_box_pack_start(GTK_BOX(mainbox), scrolled, TRUE, TRUE, 0); + + GtkWidget *bottom_box = gtk_hbox_new(FALSE, 12); + GtkWidget *close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); + gtk_box_pack_start(GTK_BOX(mainbox), bottom_box, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(bottom_box), close_button, FALSE, FALSE, 0); + + + GUI *gui = new GUI; // wil be deleted by the close button or delete-event + gui->procdata = procdata; + gui->model = model; + gui->window = GTK_WINDOW(dialog); + gui->entry = GTK_ENTRY(entry); + gui->count = GTK_LABEL(count_label); + + g_signal_connect(G_OBJECT(entry), "activate", + G_CALLBACK(GUI::search_entry_activate), gui); + g_signal_connect(G_OBJECT(clear_button), "clicked", + G_CALLBACK(GUI::clear_button_clicked), gui); + g_signal_connect(G_OBJECT(search_button), "clicked", + G_CALLBACK(GUI::search_button_clicked), gui); + g_signal_connect(G_OBJECT(close_button), "clicked", + G_CALLBACK(GUI::close_button_clicked), 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_widget_show_all(dialog); +} + diff --git a/src/lsof.h b/src/lsof.h new file mode 100644 index 0000000..ad7f111 --- /dev/null +++ b/src/lsof.h @@ -0,0 +1,9 @@ +#ifndef H_PROCMAN_LSOF_1161179202 +#define H_PROCMAN_LSOF_1161179202 + +#include <glib/gmacros.h> +#include "procman.h" + +void procman_lsof(ProcData *data); + +#endif /* H_PROCMAN_LSOF_1161179202 */ diff --git a/src/mate-system-monitor.schemas.in b/src/mate-system-monitor.schemas.in new file mode 100644 index 0000000..34bb102 --- /dev/null +++ b/src/mate-system-monitor.schemas.in @@ -0,0 +1,769 @@ +<mateconfschemafile> + + <schemalist> + <schema> + <key>/schemas/apps/procman/width</key> + <applyto>/apps/procman/width</applyto> + <owner>procman</owner> + <type>int</type> + <default>440</default> + <locale name="C"> + <short>Main Window width</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/height</key> + <applyto>/apps/procman/height</applyto> + <owner>procman</owner> + <type>int</type> + <default>495</default> + <locale name="C"> + <short>Main Window height</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/show_tree</key> + <applyto>/apps/procman/show_tree</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process dependencies in tree form</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/solaris_mode</key> + <applyto>/apps/procman/solaris_mode</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Solaris mode for CPU percentage</short> + <long>If TRUE, system-monitor operates in 'Solaris mode' where a task's cpu usage is divided by the total number of CPUs. Else it operates in 'Irix mode'.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/smooth_refresh</key> + <applyto>/apps/procman/smooth_refresh</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Enable/Disable smooth refresh</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/kill_dialog</key> + <applyto>/apps/procman/kill_dialog</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show warning dialog when killing processes</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/update_interval</key> + <applyto>/apps/procman/update_interval</applyto> + <owner>procman</owner> + <type>int</type> + <default>3000</default> + <locale name="C"> + <short>Time in milliseconds between updates of the process view</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/graph_update_interval</key> + <applyto>/apps/procman/graph_update_interval</applyto> + <owner>procman</owner> + <type>int</type> + <default>1000</default> + <locale name="C"> + <short>Time in milliseconds between updates of the graphs</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/show_all_fs</key> + <applyto>/apps/procman/show_all_fs</applyto> + <owner>procman</owner> + <type>bool</type> + <default>False</default> + <locale name="C"> + <short>Whether information about all filesystems should be displayed</short> + <long>Whether to display information about all filesystems (including types like 'autofs' and 'procfs'). Useful for getting a list of all currently mounted filesystems.</long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/disks_interval</key> + <applyto>/apps/procman/disks_interval</applyto> + <owner>procman</owner> + <type>int</type> + <default>5000</default> + <locale name="C"> + <short>Time in milliseconds between updates of the devices list</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/view_as</key> + <applyto>/apps/procman/view_as</applyto> + <owner>procman</owner> + <type>int</type> + <default>1</default> + <locale name="C"> + <short>Determines which processes to show by default. 0 is All, 1 is user, and 2 is active</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/current_tab</key> + <applyto>/apps/procman/current_tab</applyto> + <owner>procman</owner> + <type>int</type> + <default>2</default> + <locale name="C"> + <short>Saves the currently viewed tab</short> + <long>0 for the System Info, 1 for the processes list, 2 for the resources and 3 for the disks list</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/cpu_color0</key> + <applyto>/apps/procman/cpu_color0</applyto> + <owner>procman</owner> + <type>string</type> + <default>#FF6E00</default> + <locale name="C"> + <short>Default graph cpu color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/cpu_color1</key> + <applyto>/apps/procman/cpu_color1</applyto> + <owner>procman</owner> + <type>string</type> + <default>#CB0C29</default> + <locale name="C"> + <short>Default graph cpu color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/cpu_color2</key> + <applyto>/apps/procman/cpu_color2</applyto> + <owner>procman</owner> + <type>string</type> + <default>#49A835</default> + <locale name="C"> + <short>Default graph cpu color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/cpu_color3</key> + <applyto>/apps/procman/cpu_color3</applyto> + <owner>procman</owner> + <type>string</type> + <default>#2D7DB3</default> + <locale name="C"> + <short>Default graph cpu color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/mem_color</key> + <applyto>/apps/procman/mem_color</applyto> + <owner>procman</owner> + <type>string</type> + <default>#AB1852</default> + <locale name="C"> + <short>Default graph mem color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/swap_color</key> + <applyto>/apps/procman/swap_color</applyto> + <owner>procman</owner> + <type>string</type> + <default>#49A835</default> + <locale name="C"> + <short>Default graph swap color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/net_in_color</key> + <applyto>/apps/procman/net_in_color</applyto> + <owner>procman</owner> + <type>string</type> + <default>#2D7DB3</default> + <locale name="C"> + <short>Default graph incoming network traffic color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/net_out_color</key> + <applyto>/apps/procman/net_out_color</applyto> + <owner>procman</owner> + <type>string</type> + <default>#844798</default> + <locale name="C"> + <short>Default graph outgoing network traffic color</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/sort_col</key> + <applyto>/apps/procman/proctree/sort_col</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Process view sort column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/columns_order</key> + <applyto>/apps/procman/proctree/columns_order</applyto> + <owner>procman</owner> + <type>list</type> + <list_type>int</list_type> + <locale name="C"> + <short>Process view columns order</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/sort_order</key> + <applyto>/apps/procman/proctree/sort_order</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Process view sort order</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_0_width</key> + <applyto>/apps/procman/proctree/col_0_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>198</default> + <locale name="C"> + <short>Width of process 'name' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_0_visible</key> + <applyto>/apps/procman/proctree/col_0_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show process 'name' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_1_width</key> + <applyto>/apps/procman/proctree/col_1_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Width of process 'owner' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_1_visible</key> + <applyto>/apps/procman/proctree/col_1_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'owner' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_2_width</key> + <applyto>/apps/procman/proctree/col_2_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>37</default> + <locale name="C"> + <short>Width of process 'status' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_2_visible</key> + <applyto>/apps/procman/proctree/col_2_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show process 'status' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_3_width</key> + <applyto>/apps/procman/proctree/col_3_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Width of process 'virtual memory' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_3_visible</key> + <applyto>/apps/procman/proctree/col_3_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'virtual memory' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_4_width</key> + <applyto>/apps/procman/proctree/col_4_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>53</default> + <locale name="C"> + <short>Width of process 'resident memory' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_4_visible</key> + <applyto>/apps/procman/proctree/col_4_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'resident memory' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_5_width</key> + <applyto>/apps/procman/proctree/col_5_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Width of process 'writable memory' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_5_visible</key> + <applyto>/apps/procman/proctree/col_5_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'writable memory' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_6_width</key> + <applyto>/apps/procman/proctree/col_6_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Width of process 'shared memory' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_6_visible</key> + <applyto>/apps/procman/proctree/col_6_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'shared memory' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_7_width</key> + <applyto>/apps/procman/proctree/col_7_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Width of process 'X server memory' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_7_visible</key> + <applyto>/apps/procman/proctree/col_7_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'X server memory' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_8_width</key> + <applyto>/apps/procman/proctree/col_8_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Width of process 'CPU %' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_8_visible</key> + <applyto>/apps/procman/proctree/col_8_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show process 'CPU %' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_9_width</key> + <applyto>/apps/procman/proctree/col_9_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>50</default> + <locale name="C"> + <short>Width of process 'CPU time' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_9_visible</key> + <applyto>/apps/procman/proctree/col_9_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'CPU time' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_10_width</key> + <applyto>/apps/procman/proctree/col_10_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Width of process 'start time' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_10_visible</key> + <applyto>/apps/procman/proctree/col_10_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'start time' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_11_width</key> + <applyto>/apps/procman/proctree/col_11_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>48</default> + <locale name="C"> + <short>Width of process 'nice' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_11_visible</key> + <applyto>/apps/procman/proctree/col_11_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show process 'nice' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_12_width</key> + <applyto>/apps/procman/proctree/col_12_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>48</default> + <locale name="C"> + <short>Width of process 'PID' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_12_visible</key> + <applyto>/apps/procman/proctree/col_12_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show process 'PID' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_13_width</key> + <applyto>/apps/procman/proctree/col_13_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>48</default> + <locale name="C"> + <short>Width of process 'SELinux security context' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_13_visible</key> + <applyto>/apps/procman/proctree/col_13_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'SELinux security context' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_14_width</key> + <applyto>/apps/procman/proctree/col_14_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>48</default> + <locale name="C"> + <short>Width of process 'arguments' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_14_visible</key> + <applyto>/apps/procman/proctree/col_14_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>FALSE</default> + <locale name="C"> + <short>Show process 'arguments' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_15_width</key> + <applyto>/apps/procman/proctree/col_15_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>48</default> + <locale name="C"> + <short>Width of process 'estimated memory usage' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_15_visible</key> + <applyto>/apps/procman/proctree/col_15_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show process 'estimated memory usage' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/proctree/col_16_width</key> + <applyto>/apps/procman/proctree/col_16_width</applyto> + <owner>procman</owner> + <type>int</type> + <default>48</default> + <locale name="C"> + <short>Width of process 'Waiting Channel' column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/proctree/col_16_visible</key> + <applyto>/apps/procman/proctree/col_16_visible</applyto> + <owner>procman</owner> + <type>bool</type> + <default>TRUE</default> + <locale name="C"> + <short>Show process 'Waiting Channel' column on startup</short> + <long></long> + </locale> + </schema> + + + <schema> + <key>/schemas/apps/procman/disktreenew/sort_col</key> + <applyto>/apps/procman/disktreenew/sort_col</applyto> + <owner>procman</owner> + <type>int</type> + <default>1</default> + <locale name="C"> + <short>Process view sort column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/disktreenew/sort_order</key> + <applyto>/apps/procman/disktreenew/sort_order</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Process view sort order</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/disktreenew/columns_order</key> + <applyto>/apps/procman/disktreenew/columns_order</applyto> + <owner>procman</owner> + <type>list</type> + <list_type>int</list_type> + <locale name="C"> + <short>Disk view columns order</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/memmapstree/sort_col</key> + <applyto>/apps/procman/memmapstree/sort_col</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Process view sort column</short> + <long></long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/procman/memmapstree/sort_order</key> + <applyto>/apps/procman/memmapstree/sort_order</applyto> + <owner>procman</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Process view sort order</short> + <long></long> + </locale> + </schema> + + </schemalist> + +</mateconfschemafile> diff --git a/src/mateconf-keys.cpp b/src/mateconf-keys.cpp new file mode 100644 index 0000000..8fbb379 --- /dev/null +++ b/src/mateconf-keys.cpp @@ -0,0 +1,14 @@ +#include "mateconf-keys.h" + + +namespace procman +{ + namespace mateconf + { + const std::string root("/apps/procman"); + const std::string solaris_mode(root + "/solaris_mode"); + const std::string open_files_tree_prefix(root + "/openfilestree"); + const std::string network_in_bits(root + "/network_in_bits"); + } +} + diff --git a/src/mateconf-keys.h b/src/mateconf-keys.h new file mode 100644 index 0000000..400065c --- /dev/null +++ b/src/mateconf-keys.h @@ -0,0 +1,19 @@ +#ifndef H_PROCMAN_MATECONF_KEYS_1177430397 +#define H_PROCMAN_MATECONF_KEYS_1177430397 + +#include <string> + + +namespace procman +{ + namespace mateconf + { + extern const std::string root; + extern const std::string solaris_mode; + extern const std::string open_files_tree_prefix; + extern const std::string network_in_bits; + } +} + + +#endif // H_PROCMAN_MATECONF_KEYS_1177430397 diff --git a/src/memmaps.cpp b/src/memmaps.cpp new file mode 100644 index 0000000..3ca8229 --- /dev/null +++ b/src/memmaps.cpp @@ -0,0 +1,663 @@ +#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 "procman.h" +#include "memmaps.h" +#include "proctable.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; + } + }; + + + + +#if 0 + + struct ColumnState + { + unsigned visible; + unsigned id; + unsigned width; + + int pack() const + { + unsigned p = 0; + p |= (this->visible & 0x0001) << 24; + p |= (this->id & 0x00ff) << 16; + p |= (this->width & 0xffff); + return p; + } + + void unpack(int i) + { + this->visible = 0x0001 & (i >> 24); + this->id = 0x00ff & (i >> 16); + this->width = 0xffff & i; + } + }; + + + void + procman_save_tree_state2(MateConfClient *client, GtkWidget *tree, const gchar *cprefix) + { + const string prefix(cprefix); + + GtkTreeModel *model; + gint sort_col; + GtkSortType order; + + g_assert(tree); + g_assert(prefix != ""); + + model = gtk_tree_view_get_model(GTK_TREE_VIEW (tree)); + + if (gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model), &sort_col, &order)) { + mateconf_client_set_int(client, (prefix + "/sort_col").c_str(), sort_col, 0); + mateconf_client_set_int(client, (prefix + "/sort_order").c_str(), order, 0); + } + + GList * const columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(tree)); + + GSList *list = 0; + + for (GList *it = columns; it; it = it->next) + { + GtkTreeViewColumn *column; + ColumnState cs; + + column = static_cast<GtkTreeViewColumn*>(it->data); + cs.id = gtk_tree_view_column_get_sort_column_id(column); + cs.visible = gtk_tree_view_column_get_visible(column); + cs.width = gtk_tree_view_column_get_width(column); + + list = g_slist_append(list, GINT_TO_POINTER(cs.pack())); + } + + g_list_free(columns); + + GError *error = 0; + + if (not mateconf_client_set_list(client, (prefix + "/columns").c_str(), + MATECONF_VALUE_INT, list, + &error)) { + g_critical("Failed to save tree state %s : %s", + prefix.c_str(), + error->message); + g_error_free(error); + } + + g_slist_free(list); + } + + + gboolean procman_get_tree_state2(MateConfClient *client, GtkWidget *tree, const gchar *cprefix) + { + const string prefix(cprefix); + GtkTreeModel *model; + + gint sort_col; + GtkSortType order; + + g_assert(tree); + g_assert(prefix != ""); + + if (!mateconf_client_dir_exists(client, prefix.c_str(), 0)) + return FALSE; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree)); + + sort_col = mateconf_client_get_int(client, (prefix + "/sort_col").c_str(), 0); + sort_order = mateconf_client_get_int(client, (prefix + "/sort_order").c_str(), 0); + + if (sort_col != -1) + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), sort_col, order); + + proctable_set_columns_order(GTK_TREE_VIEW(tree), order); + + GSlist *list = mateconf_client_get_list(client, (prefix + "/columns").c_str(), + MATECONF_VALUE_INT, 0); + + + for (GSList *it = list; it; it = it->next) { + ColumnState cs; + cs.unpack(GPOINTER_TO_INT(it->data)); + + GtkTreeViewColumn *column; + column = gtk_tree_view_get_column(GTK_TREE_VIEW(tree), cs.id); + + if (!column) + continue; + + gtk_tree_view_column_set_visible(column, cs.visible); + if (cs.visible) + gtk_tree_view_column_set_fixed_width(column, MAX(10, cs.width)); + } + + g_slist_free(list); + + + GList * const columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(tree)); + + for (GList * it = columns; it; it = it->next) + { + GtkTreeViewColumn *column = static_cast<GtkTreeViewColumn*>(it->data); + unsigned id = gtk_tree_view_column_get_sort_column_id(column); + + ColumnState &cs(states[id]); + + + + key = g_strdup_printf("%s/col_%d_width", prefix, id); + value = mateconf_client_get (client, key, NULL); + g_free (key); + + if (value != NULL) { + width = mateconf_value_get_int(value); + mateconf_value_free (value); + + key = g_strdup_printf ("%s/col_%d_visible", prefix, id); + visible = mateconf_client_get_bool (client, key, NULL); + g_free (key); + + column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), id); + if(!column) continue; + gtk_tree_view_column_set_visible (column, visible); + if (visible) { + /* ensure column is really visible */ + width = MAX(width, 10); + gtk_tree_view_column_set_fixed_width(column, width); + } + } + } + + g_list_free(columns); + + return TRUE; + } + + + +#endif + + + + 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; + GtkWidget *tree; + MateConfClient *client; + ProcInfo *info; + OffsetFormater format; + mutable InodeDevices devices; + const char * const key; + + MemMapsData(GtkWidget *a_tree, MateConfClient *a_client) + : tree(a_tree), + client(a_client), + key("/apps/procman/memmapstree2") + { + procman_get_tree_state(this->client, this->tree, this->key); + } + + ~MemMapsData() + { + procman_save_tree_state(this->client, this->tree, this->key); + } + }; +} + + +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 not (*this)(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 gboolean window_delete_event(GtkWidget *, GdkEvent *, gpointer data) +{ + MemMapsData * const mmdata = static_cast<MemMapsData*>(data); + + g_source_remove (mmdata->timer); + + delete mmdata; + return FALSE; +} + + +static MemMapsData* +create_memmapsdata (ProcData *procdata) +{ + GtkWidget *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 */ + ); + + tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tree), TRUE); + g_object_unref (G_OBJECT (model)); + + 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: + case MMAP_COL_DEVICE: + g_object_set(cell, "family", "monospace", NULL); + break; + } + } + + return new MemMapsData(tree, procdata->client); +} + + +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) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + MemMapsData *mmdata; + GtkWidget *memmapsdialog; + GtkWidget *dialog_vbox, *vbox; + GtkWidget *label; + GtkWidget *scrolled; + ProcInfo *info; + + gtk_tree_model_get (model, iter, COL_POINTER, &info, -1); + + if (!info) + return; + + mmdata = create_memmapsdata (procdata); + mmdata->info = info; + + memmapsdialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_transient_for(GTK_WINDOW(memmapsdialog), GTK_WINDOW(procdata->app)); + gtk_window_set_destroy_with_parent(GTK_WINDOW(memmapsdialog), TRUE); + // gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_title(GTK_WINDOW(memmapsdialog), _("Memory Maps")); + gtk_window_set_resizable(GTK_WINDOW(memmapsdialog), TRUE); + gtk_window_set_default_size(GTK_WINDOW(memmapsdialog), 575, 400); + // gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(memmapsdialog), 12); + + GtkWidget *mainbox = gtk_vbox_new(FALSE, 12); + gtk_container_add(GTK_CONTAINER(memmapsdialog), mainbox); + + vbox = mainbox; + gtk_box_set_spacing (GTK_BOX (vbox), 2); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + dialog_vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), 5); + gtk_box_pack_start (GTK_BOX (vbox), dialog_vbox, TRUE, TRUE, 0); + + + label = procman_make_label_for_mmaps_or_ofiles ( + _("_Memory maps for process \"%s\" (PID %u):"), + info->name, + info->pid); + + gtk_box_pack_start (GTK_BOX (dialog_vbox), label, FALSE, TRUE, 0); + + + scrolled = 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), mmdata->tree); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), mmdata->tree); + + gtk_box_pack_start (GTK_BOX (dialog_vbox), scrolled, TRUE, TRUE, 0); + + gtk_widget_show_all (memmapsdialog); + + g_signal_connect(G_OBJECT(memmapsdialog), "delete-event", + G_CALLBACK(window_delete_event), mmdata); + + mmdata->timer = g_timeout_add_seconds (5, memmaps_timer, mmdata); + + update_memmaps_dialog (mmdata); +} + + +void +create_memmaps_dialog (ProcData *procdata) +{ + /* TODO: do we really want to open multiple dialogs ? */ + gtk_tree_selection_selected_foreach (procdata->selection, create_single_memmaps_dialog, + procdata); +} diff --git a/src/memmaps.h b/src/memmaps.h new file mode 100644 index 0000000..f0a00cd --- /dev/null +++ b/src/memmaps.h @@ -0,0 +1,9 @@ +#ifndef _PROCMAN_MEMMAPS_H_ +#define _PROCMAN_MEMMAPS_H_ + +#include <glib.h> +#include "procman.h" + +void create_memmaps_dialog (ProcData *procdata); + +#endif /* _PROCMAN_MEMMAPS_H_ */ diff --git a/src/openfiles.cpp b/src/openfiles.cpp new file mode 100644 index 0000000..8b76858 --- /dev/null +++ b/src/openfiles.cpp @@ -0,0 +1,394 @@ +#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 "procman.h" +#include "openfiles.h" +#include "proctable.h" +#include "util.h" +#include "mateconf-keys.h" + +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 == o1->type); /* XXX! */ +} + + +static void +update_openfiles_dialog (GtkWidget *tree) +{ + ProcInfo *info; + GtkTreeModel *model; + glibtop_open_files_entry *openfiles; + glibtop_proc_open_files procmap; + GHashTable *new_maps; + guint i; + + info = static_cast<ProcInfo*>(g_object_get_data (G_OBJECT (tree), "selected_info")); + + 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) +{ + GtkWidget *tree = static_cast<GtkWidget*>(data); + MateConfClient *client; + guint timer; + + client = static_cast<MateConfClient*>(g_object_get_data (G_OBJECT (tree), "client")); + procman_save_tree_state (client, tree, procman::mateconf::open_files_tree_prefix.c_str()); + + timer = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (tree), "timer")); + g_source_remove (timer); + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + return ; +} + + +static GtkWidget * +create_openfiles_tree (ProcData *procdata) +{ + GtkWidget *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 */ + ); + + tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tree), TRUE); + 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); + } + +#if 0 + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model), + COL_VMSZ, + sort_ints, + GINT_TO_POINTER (COL_FD), + NULL); +/*gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), + 0, + GTK_SORT_ASCENDING);*/ +#endif + + procman_get_tree_state (procdata->client, tree, procman::mateconf::open_files_tree_prefix.c_str()); + + return tree; + +} + + +static gboolean +openfiles_timer (gpointer data) +{ + GtkWidget *tree = static_cast<GtkWidget*>(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) +{ + ProcData *procdata = static_cast<ProcData*>(data); + GtkWidget *openfilesdialog; + GtkWidget *dialog_vbox, *vbox; + GtkWidget *cmd_hbox; + GtkWidget *label; + GtkWidget *scrolled; + GtkWidget *tree; + ProcInfo *info; + guint timer; + + gtk_tree_model_get (model, iter, COL_POINTER, &info, -1); + + if (!info) + return; + + openfilesdialog = gtk_dialog_new_with_buttons (_("Open Files"), NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + gtk_window_set_resizable (GTK_WINDOW (openfilesdialog), TRUE); + gtk_window_set_default_size (GTK_WINDOW (openfilesdialog), 575, 400); + gtk_dialog_set_has_separator (GTK_DIALOG (openfilesdialog), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (openfilesdialog), 5); + + vbox = gtk_dialog_get_content_area (GTK_DIALOG (openfilesdialog)); + gtk_box_set_spacing (GTK_BOX (vbox), 2); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + dialog_vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), 5); + gtk_box_pack_start (GTK_BOX (vbox), dialog_vbox, TRUE, TRUE, 0); + + cmd_hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (dialog_vbox), cmd_hbox, FALSE, FALSE, 0); + + + label = procman_make_label_for_mmaps_or_ofiles ( + _("_Files opened by process \"%s\" (PID %u):"), + info->name, + info->pid); + + gtk_box_pack_start (GTK_BOX (cmd_hbox),label, FALSE, FALSE, 0); + + + scrolled = 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); + + tree = create_openfiles_tree (procdata); + gtk_container_add (GTK_CONTAINER (scrolled), tree); + g_object_set_data (G_OBJECT (tree), "selected_info", info); + g_object_set_data (G_OBJECT (tree), "client", procdata->client); + + gtk_box_pack_start (GTK_BOX (dialog_vbox), scrolled, TRUE, TRUE, 0); + gtk_widget_show_all (scrolled); + + g_signal_connect (G_OBJECT (openfilesdialog), "response", + G_CALLBACK (close_openfiles_dialog), tree); + + gtk_widget_show_all (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); + +} + + +void +create_openfiles_dialog (ProcData *procdata) +{ + gtk_tree_selection_selected_foreach (procdata->selection, create_single_openfiles_dialog, + procdata); +} diff --git a/src/openfiles.h b/src/openfiles.h new file mode 100644 index 0000000..38b1cc8 --- /dev/null +++ b/src/openfiles.h @@ -0,0 +1,10 @@ +#ifndef _OPENFILES_H_ +#define _OPENFILES_H_ + +#include <glib/gtypes.h> + +#include "procman.h" + +void create_openfiles_dialog (ProcData *procdata); + +#endif diff --git a/src/prettytable.cpp b/src/prettytable.cpp new file mode 100644 index 0000000..25f6625 --- /dev/null +++ b/src/prettytable.cpp @@ -0,0 +1,256 @@ +#include <config.h> +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include <libwnck/libwnck.h> +#include <dirent.h> +#include <sys/stat.h> +#include <stdio.h> +#include <string.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <glibtop/procstate.h> + +#include <vector> + +#include "prettytable.h" +#include "defaulttable.h" +#include "proctable.h" +#include "util.h" + + +namespace +{ + const unsigned APP_ICON_SIZE = 16; +} + + +PrettyTable::PrettyTable() +{ + 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); +} + + +PrettyTable::~PrettyTable() +{ +} + + +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 = that->theme->load_icon(icon_name, APP_ICON_SIZE, Gtk::ICON_LOOKUP_USE_BUILTIN); + + 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 = ProcInfo::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); +} + + + +Glib::RefPtr<Gdk::Pixbuf> +PrettyTable::get_icon_from_theme(const ProcInfo &info) +{ + return this->theme->load_icon(info.name, APP_ICON_SIZE, Gtk::ICON_LOOKUP_USE_BUILTIN); +} + + +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 = this->theme->load_icon(name, APP_ICON_SIZE, Gtk::ICON_LOOKUP_USE_BUILTIN); + if (pix) + this->defaults[name] = pix; + } else + pix = it->second; + } + + return pix; +} + + + +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; +} + + + +Glib::RefPtr<Gdk::Pixbuf> +PrettyTable::get_icon_from_name(const ProcInfo &info) +{ + return this->theme->load_icon(info.name, APP_ICON_SIZE, Gtk::ICON_LOOKUP_USE_BUILTIN); +} + + +Glib::RefPtr<Gdk::Pixbuf> +PrettyTable::get_icon_dummy(const ProcInfo &) +{ + return this->theme->load_icon("application-x-executable", APP_ICON_SIZE, Gtk::ICON_LOOKUP_USE_BUILTIN); +} + + +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 this->theme->load_icon("applications-system", APP_ICON_SIZE, Gtk::ICON_LOOKUP_USE_BUILTIN); + + 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_wnck); + 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, info.pid, e.what()); + continue; + } + catch (Glib::Exception& e) { + g_warning("Failed to load icon for %s(%u) : %s", info.name, 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..7884cdd --- /dev/null +++ b/src/prettytable.h @@ -0,0 +1,62 @@ +// -*- c++ -*- + +#ifndef _PROCMAN_PRETTYTABLE_H_ +#define _PROCMAN_PRETTYTABLE_H_ + +#include <glib.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <glibmm/refptr.h> +#include <gdkmm/pixbuf.h> + +#include <map> +#include <string> + +extern "C" { +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include <libwnck/libwnck.h> +} + +#include "iconthemewrapper.h" + +class ProcInfo; + +using std::string; + + + +class PrettyTable +{ + public: + PrettyTable(); + ~PrettyTable(); + + void set_icon(ProcInfo &); + +private: + + 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); + + + 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_wnck(const ProcInfo &); + 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); + + typedef std::map<string, Glib::RefPtr<Gdk::Pixbuf> > IconCache; + typedef std::map<pid_t, Glib::RefPtr<Gdk::Pixbuf> > IconsForPID; + + IconsForPID apps; + IconCache defaults; + procman::IconThemeWrapper theme; +}; + + +#endif /* _PROCMAN_PRETTYTABLE_H_ */ diff --git a/src/procactions.cpp b/src/procactions.cpp new file mode 100644 index 0000000..8e79608 --- /dev/null +++ b/src/procactions.cpp @@ -0,0 +1,190 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + + +#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 "procman.h" +#include "proctable.h" +#include "procdialogs.h" +#include "callbacks.h" + + +static void +renice_single_process (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + const struct ReniceArgs * const args = static_cast<ReniceArgs*>(data); + + ProcInfo *info = NULL; + gint error; + int saved_errno; + gchar *error_msg; + GtkWidget *dialog; + + gtk_tree_model_get (model, iter, COL_POINTER, &info, -1); + + if (!info) + return; + + error = setpriority (PRIO_PROCESS, info->pid, args->nice_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->procdata, info->pid, + args->nice_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->nice_value, g_strerror(saved_errno)); + + 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 (dialog); + g_free (error_msg); +} + + +void +renice (ProcData *procdata, int nice) +{ + struct ReniceArgs args = { procdata, 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 + */ + g_source_remove(procdata->timeout); + + gtk_tree_selection_selected_foreach(procdata->selection, renice_single_process, + &args); + + procdata->timeout = g_timeout_add(procdata->config.update_interval, + cb_timeout, + procdata); + + proctable_update_all (procdata); +} + + + + +static void +kill_single_process (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + const struct KillArgs * const args = static_cast<KillArgs*>(data); + char *error_msg; + ProcInfo *info; + int error; + int saved_errno; + GtkWidget *dialog; + + gtk_tree_model_get (model, iter, COL_POINTER, &info, -1); + + if (!info) + return; + + error = kill (info->pid, args->signal); + + /* 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->procdata, info->pid, + args->signal); + + 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->signal, g_strerror(saved_errno)); + + 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 (dialog); + g_free (error_msg); +} + + +void +kill_process (ProcData *procdata, int sig) +{ + struct KillArgs args = { procdata, 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 + */ + g_source_remove (procdata->timeout); + + gtk_tree_selection_selected_foreach (procdata->selection, kill_single_process, + &args); + + procdata->timeout = g_timeout_add (procdata->config.update_interval, + cb_timeout, + procdata); + proctable_update_all (procdata); +} diff --git a/src/procactions.h b/src/procactions.h new file mode 100644 index 0000000..a4190d5 --- /dev/null +++ b/src/procactions.h @@ -0,0 +1,28 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ +#ifndef _PROCACTIONS_H_ +#define _PROCACTIONS_H_ + +#include "procman.h" + +void renice (ProcData *procdata, int nice); +void kill_process (ProcData *procdata, int sig); + +#endif + diff --git a/src/procdialogs.cpp b/src/procdialogs.cpp new file mode 100644 index 0000000..fef8b5f --- /dev/null +++ b/src/procdialogs.cpp @@ -0,0 +1,842 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + + +#include <config.h> + +#include <glib/gi18n.h> + +#include <signal.h> +#include <string.h> +#include "procdialogs.h" +#include "proctable.h" +#include "callbacks.h" +#include "prettytable.h" +#include "procactions.h" +#include "util.h" +#include "load-graph.h" +#include "mateconf-keys.h" +#include "procman_matesu.h" +#include "procman_gksu.h" + +static GtkWidget *renice_dialog = NULL; +static GtkWidget *prefs_dialog = NULL; +static gint new_nice_value = 0; + + +static void +kill_dialog_button_pressed (GtkDialog *dialog, gint id, gpointer data) +{ + struct KillArgs *kargs = static_cast<KillArgs*>(data); + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + if (id == GTK_RESPONSE_OK) + kill_process (kargs->procdata, kargs->signal); + + g_free (kargs); +} + +void +procdialog_create_kill_dialog (ProcData *procdata, int signal) +{ + GtkWidget *kill_alert_dialog; + gchar *primary, *secondary, *button_text; + struct KillArgs *kargs; + + kargs = g_new(KillArgs, 1); + kargs->procdata = procdata; + kargs->signal = signal; + + + if (signal == SIGKILL) { + /*xgettext: primary alert message*/ + primary = _("Kill the selected process?"); + /*xgettext: secondary alert message*/ + secondary = _("Killing a process may destroy data, break the " + "session or introduce a security risk. " + "Only unresponding processes should be killed."); + button_text = _("_Kill Process"); + } + else { + /*xgettext: primary alert message*/ + primary = _("End the selected process?"); + /*xgettext: secondary alert message*/ + secondary = _("Ending a process may destroy data, break the " + "session or introduce a security risk. " + "Only unresponding processes should be ended."); + button_text = _("_End Process"); + } + + kill_alert_dialog = gtk_message_dialog_new (GTK_WINDOW (procdata->app), + static_cast<GtkDialogFlags>(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_MESSAGE_WARNING, + GTK_BUTTONS_NONE, + "%s", + primary); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (kill_alert_dialog), + "%s", + secondary); + + gtk_dialog_add_buttons (GTK_DIALOG (kill_alert_dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + button_text, GTK_RESPONSE_OK, + NULL); + + 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 (kill_alert_dialog); +} + +static gchar * +get_nice_level (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)"); +} + +static void +renice_scale_changed (GtkAdjustment *adj, gpointer data) +{ + GtkWidget *label = GTK_WIDGET (data); + + new_nice_value = int(gtk_adjustment_get_value (adj)); + gtk_label_set_text (GTK_LABEL (label), get_nice_level (new_nice_value)); + +} + +static void +renice_dialog_button_pressed (GtkDialog *dialog, gint id, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + + if (id == 100) { + if (new_nice_value == -100) + return; + renice(procdata, new_nice_value); + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); + renice_dialog = NULL; +} + +void +procdialog_create_renice_dialog (ProcData *procdata) +{ + ProcInfo *info = procdata->selected_process; + GtkWidget *dialog = NULL; + GtkWidget *dialog_vbox; + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *priority_label; + GtkWidget *table; + GtkObject *renice_adj; + GtkWidget *hscale; + GtkWidget *button; + GtkWidget *align; + GtkWidget *icon; + gchar *text; + + if (renice_dialog) + return; + + if (!info) + return; + + dialog = gtk_dialog_new_with_buttons (_("Change Priority"), NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + renice_dialog = dialog; + gtk_window_set_resizable (GTK_WINDOW (renice_dialog), FALSE); + gtk_dialog_set_has_separator (GTK_DIALOG (renice_dialog), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (renice_dialog), 5); + + button = gtk_button_new (); + gtk_widget_set_can_default (button, TRUE); + + align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (button), align); + + hbox = gtk_hbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (align), hbox); + + icon = gtk_image_new_from_stock (GTK_STOCK_OK, GTK_ICON_SIZE_BUTTON); + gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0); + + label = gtk_label_new_with_mnemonic (_("Change _Priority")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), button); + gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + gtk_dialog_add_action_widget (GTK_DIALOG (renice_dialog), button, 100); + gtk_dialog_set_default_response (GTK_DIALOG (renice_dialog), 100); + new_nice_value = -100; + + dialog_vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_set_spacing (GTK_BOX (dialog_vbox), 2); + gtk_container_set_border_width (GTK_CONTAINER (dialog_vbox), 5); + + vbox = gtk_vbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (dialog_vbox), vbox, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_col_spacings (GTK_TABLE(table), 12); + gtk_table_set_row_spacings (GTK_TABLE(table), 6); + gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0); + + label = gtk_label_new_with_mnemonic (_("_Nice value:")); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 2, + GTK_FILL, GTK_FILL, 0, 0); + + renice_adj = gtk_adjustment_new (info->nice, RENICE_VAL_MIN, RENICE_VAL_MAX, 1, 1, 0); + new_nice_value = 0; + hscale = gtk_hscale_new (GTK_ADJUSTMENT (renice_adj)); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), hscale); + gtk_scale_set_digits (GTK_SCALE (hscale), 0); + gtk_table_attach (GTK_TABLE (table), hscale, 1, 2, 0, 1, + static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL), GTK_FILL, 0, 0); + + priority_label = gtk_label_new (get_nice_level (info->nice)); + gtk_table_attach (GTK_TABLE (table), priority_label, 1, 2, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + + 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_new (_(text)); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + g_free (text); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (renice_dialog_button_pressed), procdata); + g_signal_connect (G_OBJECT (renice_adj), "value_changed", + G_CALLBACK (renice_scale_changed), priority_label); + + gtk_widget_show_all (dialog); + + +} + +static void +prefs_dialog_button_pressed (GtkDialog *dialog, gint id, gpointer data) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); + + prefs_dialog = NULL; +} + + +static void +show_kill_dialog_toggled (GtkToggleButton *button, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfClient *client = procdata->client; + + gboolean toggled; + + toggled = gtk_toggle_button_get_active (button); + + mateconf_client_set_bool (client, "/apps/procman/kill_dialog", toggled, NULL); + +} + + + +static void +solaris_mode_toggled(GtkToggleButton *button, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfClient *client = procdata->client; + gboolean toggled; + toggled = gtk_toggle_button_get_active(button); + mateconf_client_set_bool(client, procman::mateconf::solaris_mode.c_str(), toggled, NULL); +} + + +static void +network_in_bits_toggled(GtkToggleButton *button, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfClient *client = procdata->client; + gboolean toggled; + toggled = gtk_toggle_button_get_active(button); + mateconf_client_set_bool(client, procman::mateconf::network_in_bits.c_str(), toggled, NULL); +} + + + +static void +smooth_refresh_toggled(GtkToggleButton *button, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfClient *client = procdata->client; + + gboolean toggled; + + toggled = gtk_toggle_button_get_active(button); + + mateconf_client_set_bool(client, SmoothRefresh::KEY.c_str(), toggled, NULL); +} + + + +static void +show_all_fs_toggled (GtkToggleButton *button, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfClient *client = procdata->client; + + gboolean toggled; + + toggled = gtk_toggle_button_get_active (button); + + mateconf_client_set_bool (client, "/apps/procman/show_all_fs", toggled, NULL); +} + + +class SpinButtonUpdater +{ +public: + SpinButtonUpdater(const string& mateconf_key) + : mateconf_key(mateconf_key) + { } + + static gboolean callback(GtkWidget *widget, GdkEventFocus *event, gpointer data) + { + SpinButtonUpdater* updater = static_cast<SpinButtonUpdater*>(data); + updater->update(GTK_SPIN_BUTTON(widget)); + return FALSE; + } + +private: + + void update(GtkSpinButton* spin) + { + int new_value = int(1000 * gtk_spin_button_get_value(spin)); + GError* e = 0; + + if (not mateconf_client_set_int(ProcData::get_instance()->client, + this->mateconf_key.c_str(), new_value, + &e)) { + g_warning("Failed to mateconf_client_set_int %s %d : %s\n", + this->mateconf_key.c_str(), new_value, e->message); + g_error_free(e); + } + + procman_debug("set %s to %d", this->mateconf_key.c_str(), new_value); + } + + const string mateconf_key; +}; + + + + +static void +field_toggled (GtkCellRendererToggle *cell, 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; + + if (!path) + return; + + gtk_tree_model_get_iter (model, &iter, path); + + gtk_tree_model_get (model, &iter, 2, &column, -1); + toggled = gtk_cell_renderer_toggle_get_active (cell); + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, !toggled, -1); + gtk_tree_view_column_set_visible (column, !toggled); + + gtk_tree_path_free (path); + +} + +static GtkWidget * +create_field_page(GtkWidget *tree, const char* text) +{ + GtkWidget *vbox; + GtkWidget *scrolled; + GtkWidget *label; + GtkWidget *treeview; + GList *it, *columns; + GtkListStore *model; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + + vbox = gtk_vbox_new (FALSE, 6); + + label = gtk_label_new_with_mnemonic (text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + + scrolled = 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_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0); + + model = gtk_list_store_new (3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER); + + treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + gtk_container_add (GTK_CONTAINER (scrolled), treeview); + g_object_unref (G_OBJECT (model)); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), treeview); + + 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); + g_signal_connect (G_OBJECT (cell), "toggled", G_CALLBACK (field_toggled), model); + 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); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + + 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; + + title = gtk_tree_view_column_get_title (column); + if (!title) + title = _("Icon"); + + 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); + + return vbox; +} + +void +procdialog_create_preferences_dialog (ProcData *procdata) +{ + static GtkWidget *dialog = NULL; + + typedef SpinButtonUpdater SBU; + + static SBU interval_updater("/apps/procman/update_interval"); + static SBU graph_interval_updater("/apps/procman/graph_update_interval"); + static SBU disks_interval_updater("/apps/procman/disks_interval"); + + GtkWidget *notebook; + GtkWidget *proc_box; + GtkWidget *sys_box; + GtkWidget *main_vbox; + GtkWidget *vbox, *vbox2, *vbox3; + GtkWidget *hbox, *hbox2, *hbox3; + GtkWidget *label; + GtkAdjustment *adjustment; + GtkWidget *spin_button; + GtkWidget *check_button; + GtkWidget *tab_label; + GtkWidget *smooth_button; + GtkSizeGroup *size; + gfloat update; + gchar *tmp; + + if (prefs_dialog) + return; + + size = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + dialog = gtk_dialog_new_with_buttons (_("System Monitor Preferences"), + GTK_WINDOW (procdata->app), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + /* FIXME: we should not declare the window size, but let it's */ + /* driven by window childs. The problem is that the fields list */ + /* have to show at least 4 items to respect HIG. I don't know */ + /* any function to set list height by contents/items inside it. */ + gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 420); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + prefs_dialog = dialog; + + main_vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_set_spacing (GTK_BOX (main_vbox), 2); + + notebook = gtk_notebook_new (); + gtk_container_set_border_width (GTK_CONTAINER (notebook), 5); + gtk_box_pack_start (GTK_BOX (main_vbox), notebook, TRUE, TRUE, 0); + + proc_box = gtk_vbox_new (FALSE, 18); + gtk_container_set_border_width (GTK_CONTAINER (proc_box), 12); + tab_label = gtk_label_new (_("Processes")); + gtk_widget_show (tab_label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), proc_box, tab_label); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (proc_box), vbox, FALSE, FALSE, 0); + + tmp = g_strdup_printf ("<b>%s</b>", _("Behavior")); + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_label_set_markup (GTK_LABEL (label), tmp); + g_free (tmp); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (" "); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + vbox2 = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0); + + hbox2 = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + + label = gtk_label_new_with_mnemonic (_("_Update interval in seconds:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 0); + + hbox3 = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox2), hbox3, TRUE, TRUE, 0); + + update = (gfloat) procdata->config.update_interval; + adjustment = (GtkAdjustment *) gtk_adjustment_new(update / 1000.0, + MIN_UPDATE_INTERVAL / 1000, + MAX_UPDATE_INTERVAL / 1000, + 0.25, + 1.0, + 1.0); + + spin_button = gtk_spin_button_new (adjustment, 1.0, 2); + gtk_box_pack_start (GTK_BOX (hbox3), spin_button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (spin_button), "focus_out_event", + G_CALLBACK (SBU::callback), &interval_updater); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin_button); + + + hbox2 = gtk_hbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(vbox2), hbox2, FALSE, FALSE, 0); + + smooth_button = gtk_check_button_new_with_mnemonic(_("Enable _smooth refresh")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(smooth_button), + mateconf_client_get_bool(procdata->client, + SmoothRefresh::KEY.c_str(), + NULL)); + g_signal_connect(G_OBJECT(smooth_button), "toggled", + G_CALLBACK(smooth_refresh_toggled), procdata); + gtk_box_pack_start(GTK_BOX(hbox2), smooth_button, TRUE, TRUE, 0); + + + + hbox2 = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + + check_button = gtk_check_button_new_with_mnemonic (_("Alert before ending or _killing processes")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + procdata->config.show_kill_warning); + g_signal_connect (G_OBJECT (check_button), "toggled", + G_CALLBACK (show_kill_dialog_toggled), procdata); + gtk_box_pack_start (GTK_BOX (hbox2), check_button, FALSE, FALSE, 0); + + + + + hbox2 = gtk_hbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(vbox2), hbox2, FALSE, FALSE, 0); + + GtkWidget *solaris_button; + solaris_button = gtk_check_button_new_with_mnemonic(_("Solaris mode")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(solaris_button), + mateconf_client_get_bool(procdata->client, + procman::mateconf::solaris_mode.c_str(), + NULL)); + g_signal_connect(G_OBJECT(solaris_button), "toggled", + G_CALLBACK(solaris_mode_toggled), procdata); + gtk_box_pack_start(GTK_BOX(hbox2), solaris_button, TRUE, TRUE, 0); + + + + + hbox2 = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (proc_box), vbox, TRUE, TRUE, 0); + + tmp = g_strdup_printf ("<b>%s</b>", _("Information Fields")); + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_label_set_markup (GTK_LABEL (label), tmp); + g_free (tmp); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + + label = gtk_label_new (" "); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + vbox2 = create_field_page (procdata->tree, _("Process i_nformation shown in list:")); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0); + + sys_box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (sys_box), 12); + tab_label = gtk_label_new (_("Resources")); + gtk_widget_show (tab_label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), sys_box, tab_label); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (sys_box), vbox, FALSE, FALSE, 0); + + tmp = g_strdup_printf ("<b>%s</b>", _("Graphs")); + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_label_set_markup (GTK_LABEL (label), tmp); + g_free (tmp); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (" "); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + vbox2 = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0); + + hbox2 = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + + label = gtk_label_new_with_mnemonic (_("_Update interval in seconds:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 0); + gtk_size_group_add_widget (size, label); + + hbox3 = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox2), hbox3, TRUE, TRUE, 0); + + update = (gfloat) procdata->config.graph_update_interval; + adjustment = (GtkAdjustment *) gtk_adjustment_new(update / 1000.0, 0.25, + 100.0, 0.25, 1.0, 1.0); + spin_button = gtk_spin_button_new (adjustment, 1.0, 2); + g_signal_connect (G_OBJECT (spin_button), "focus_out_event", + G_CALLBACK(SBU::callback), + &graph_interval_updater); + gtk_box_pack_start (GTK_BOX (hbox3), spin_button, FALSE, FALSE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin_button); + + + GtkWidget *bits_button; + bits_button = gtk_check_button_new_with_mnemonic(_("Show network speed in bits")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bits_button), + mateconf_client_get_bool(procdata->client, + procman::mateconf::network_in_bits.c_str(), + NULL)); + g_signal_connect(G_OBJECT(bits_button), "toggled", + G_CALLBACK(network_in_bits_toggled), procdata); + gtk_box_pack_start(GTK_BOX(vbox2), bits_button, TRUE, TRUE, 0); + + + + hbox2 = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, TRUE, TRUE, 0); + + /* + * Devices + */ + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + tab_label = gtk_label_new (_("File Systems")); + gtk_widget_show (tab_label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, tab_label); + + tmp = g_strdup_printf ("<b>%s</b>", _("File Systems")); + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_label_set_markup (GTK_LABEL (label), tmp); + g_free (tmp); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + label = gtk_label_new (" "); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + vbox2 = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0); + + hbox2 = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + + label = gtk_label_new_with_mnemonic (_("_Update interval in seconds:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 0); + + hbox3 = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox2), hbox3, TRUE, TRUE, 0); + + update = (gfloat) procdata->config.disks_update_interval; + adjustment = (GtkAdjustment *) gtk_adjustment_new (update / 1000.0, 1.0, + 100.0, 1.0, 1.0, 1.0); + spin_button = gtk_spin_button_new (adjustment, 1.0, 0); + gtk_box_pack_start (GTK_BOX (hbox3), spin_button, FALSE, FALSE, 0); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin_button); + g_signal_connect (G_OBJECT (spin_button), "focus_out_event", + G_CALLBACK(SBU::callback), + &disks_interval_updater); + + + hbox2 = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + check_button = gtk_check_button_new_with_mnemonic (_("Show _all filesystems")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), + procdata->config.show_all_fs); + g_signal_connect (G_OBJECT (check_button), "toggled", + G_CALLBACK (show_all_fs_toggled), procdata); + gtk_box_pack_start (GTK_BOX (hbox2), check_button, FALSE, FALSE, 0); + + + vbox2 = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0); + + label = gtk_label_new (" "); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + + tmp = g_strdup_printf ("<b>%s</b>", _("Information Fields")); + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_label_set_markup (GTK_LABEL (label), tmp); + g_free (tmp); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + + label = gtk_label_new (" "); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + vbox3 = create_field_page (procdata->disk_list, _("File system i_nformation shown in list:")); + gtk_box_pack_start (GTK_BOX (hbox), vbox3, TRUE, TRUE, 0); + + gtk_widget_show_all (dialog); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (prefs_dialog_button_pressed), procdata); + + switch (procdata->config.current_tab) { + case PROCMAN_TAB_SYSINFO: + case PROCMAN_TAB_PROCESSES: + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 0); + break; + case PROCMAN_TAB_RESOURCES: + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 1); + break; + case PROCMAN_TAB_DISKS: + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), 2); + break; + + } +} + + + +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, + ProcData *procdata, + 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_gksu()) + ret = procman_gksu_create_root_password_dialog(command); + else if (procman_has_matesu()) + ret = procman_matesu_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..eba3212 --- /dev/null +++ b/src/procdialogs.h @@ -0,0 +1,54 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ +#ifndef _PROCDIALOGS_H_ +#define _PROCDIALOGS_H_ + + +#include <glib.h> +#include "procman.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 +#define RENICE_VAL_MIN -20 +#define RENICE_VAL_MAX 19 +#else /* ! linux */ +#define RENICE_VAL_MIN -20 +#define RENICE_VAL_MAX 20 +#endif + + +typedef enum +{ + PROCMAN_ACTION_RENICE, + PROCMAN_ACTION_KILL +} ProcmanActionType; + + +void procdialog_create_kill_dialog (ProcData *data, int signal); +void procdialog_create_renice_dialog (ProcData *data); +gboolean procdialog_create_root_password_dialog (ProcmanActionType type, + ProcData *procdata, + gint pid, gint extra_value); +void procdialog_create_memmaps_dialog (ProcData *data); +void procdialog_create_preferences_dialog (ProcData *data); + +#endif + diff --git a/src/procman.cpp b/src/procman.cpp new file mode 100644 index 0000000..16640c3 --- /dev/null +++ b/src/procman.cpp @@ -0,0 +1,773 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <config.h> + +#include <stdlib.h> + +#include <locale.h> + +#include <gtkmm/main.h> +#include <giomm/volumemonitor.h> +#include <giomm/init.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <bacon-message-connection.h> +#include <mateconf/mateconf-client.h> +#include <glibtop.h> +#include <glibtop/close.h> +#include <glibtop/loadavg.h> + +#include "load-graph.h" +#include "procman.h" +#include "interface.h" +#include "proctable.h" +#include "prettytable.h" +#include "callbacks.h" +#include "smooth_refresh.h" +#include "util.h" +#include "mateconf-keys.h" +#include "argv.h" + + +ProcData::ProcData() + : tree(NULL), + cpu_graph(NULL), + mem_graph(NULL), + net_graph(NULL), + selected_process(NULL), + timeout(0), + disk_timeout(0), + cpu_total_time(1), + cpu_total_time_last(1) +{ } + + +ProcData* ProcData::get_instance() +{ + static ProcData instance; + return &instance; +} + + +static void +tree_changed_cb (MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfValue *value = mateconf_entry_get_value (entry); + + procdata->config.show_tree = mateconf_value_get_bool (value); + + g_object_set(G_OBJECT(procdata->tree), + "show-expanders", procdata->config.show_tree, + NULL); + + proctable_clear_tree (procdata); + + proctable_update_all (procdata); +} + +static void +solaris_mode_changed_cb(MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfValue *value = mateconf_entry_get_value (entry); + + procdata->config.solaris_mode = mateconf_value_get_bool(value); + proctable_update_all (procdata); +} + + +static void +network_in_bits_changed_cb(MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfValue *value = mateconf_entry_get_value (entry); + + procdata->config.network_in_bits = mateconf_value_get_bool(value); + // force scale to be redrawn + procdata->net_graph->clear_background(); +} + + + +static void +view_as_changed_cb (MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + MateConfValue *value = mateconf_entry_get_value (entry); + + procdata->config.whose_process = mateconf_value_get_int (value); + procdata->config.whose_process = CLAMP (procdata->config.whose_process, 0, 2); + proctable_clear_tree (procdata); + proctable_update_all (procdata); + +} + +static void +warning_changed_cb (MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + const gchar *key = mateconf_entry_get_key (entry); + MateConfValue *value = mateconf_entry_get_value (entry); + + if (g_str_equal (key, "/apps/procman/kill_dialog")) { + procdata->config.show_kill_warning = mateconf_value_get_bool (value); + } +} + +static void +timeouts_changed_cb (MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData *procdata = static_cast<ProcData*>(data); + const gchar *key = mateconf_entry_get_key (entry); + MateConfValue *value = mateconf_entry_get_value (entry); + + if (g_str_equal (key, "/apps/procman/update_interval")) { + procdata->config.update_interval = mateconf_value_get_int (value); + procdata->config.update_interval = + MAX (procdata->config.update_interval, 1000); + + procdata->smooth_refresh->reset(); + + if(procdata->timeout) { + g_source_remove (procdata->timeout); + procdata->timeout = g_timeout_add (procdata->config.update_interval, + cb_timeout, + procdata); + } + } + else if (g_str_equal (key, "/apps/procman/graph_update_interval")){ + procdata->config.graph_update_interval = mateconf_value_get_int (value); + procdata->config.graph_update_interval = + MAX (procdata->config.graph_update_interval, + 250); + load_graph_change_speed(procdata->cpu_graph, + procdata->config.graph_update_interval); + load_graph_change_speed(procdata->mem_graph, + procdata->config.graph_update_interval); + load_graph_change_speed(procdata->net_graph, + procdata->config.graph_update_interval); + } + else if (g_str_equal(key, "/apps/procman/disks_interval")) { + + procdata->config.disks_update_interval = mateconf_value_get_int (value); + procdata->config.disks_update_interval = + MAX (procdata->config.disks_update_interval, 1000); + + if(procdata->disk_timeout) { + g_source_remove (procdata->disk_timeout); + procdata->disk_timeout = \ + g_timeout_add (procdata->config.disks_update_interval, + cb_update_disks, + procdata); + } + } + else { + g_assert_not_reached(); + } +} + +static void +color_changed_cb (MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + const gchar *key = mateconf_entry_get_key (entry); + MateConfValue *value = mateconf_entry_get_value (entry); + const gchar *color = mateconf_value_get_string (value); + + if (g_str_has_prefix (key, "/apps/procman/cpu_color")) { + for (int i = 0; i < GLIBTOP_NCPU; i++) { + string cpu_key = make_string(g_strdup_printf("/apps/procman/cpu_color%d", i)); + if (cpu_key == key) { + gdk_color_parse (color, &procdata->config.cpu_color[i]); + procdata->cpu_graph->colors.at(i) = procdata->config.cpu_color[i]; + break; + } + } + } + else if (g_str_equal (key, "/apps/procman/mem_color")) { + gdk_color_parse (color, &procdata->config.mem_color); + procdata->mem_graph->colors.at(0) = procdata->config.mem_color; + } + else if (g_str_equal (key, "/apps/procman/swap_color")) { + gdk_color_parse (color, &procdata->config.swap_color); + procdata->mem_graph->colors.at(1) = procdata->config.swap_color; + } + else if (g_str_equal (key, "/apps/procman/net_in_color")) { + gdk_color_parse (color, &procdata->config.net_in_color); + procdata->net_graph->colors.at(0) = procdata->config.net_in_color; + } + else if (g_str_equal (key, "/apps/procman/net_out_color")) { + gdk_color_parse (color, &procdata->config.net_out_color); + procdata->net_graph->colors.at(1) = procdata->config.net_out_color; + } + else { + g_assert_not_reached(); + } +} + + + +static void +show_all_fs_changed_cb (MateConfClient *client, guint id, MateConfEntry *entry, gpointer data) +{ + ProcData * const procdata = static_cast<ProcData*>(data); + MateConfValue *value = mateconf_entry_get_value (entry); + + procdata->config.show_all_fs = mateconf_value_get_bool (value); + + cb_update_disks (data); +} + + +static ProcData * +procman_data_new (MateConfClient *client) +{ + + ProcData *pd; + gchar *color; + gint swidth, sheight; + gint i; + glibtop_cpu cpu; + + pd = ProcData::get_instance(); + + pd->config.width = mateconf_client_get_int (client, "/apps/procman/width", NULL); + pd->config.height = mateconf_client_get_int (client, "/apps/procman/height", NULL); + pd->config.show_tree = mateconf_client_get_bool (client, "/apps/procman/show_tree", NULL); + mateconf_client_notify_add (client, "/apps/procman/show_tree", tree_changed_cb, + pd, NULL, NULL); + + pd->config.solaris_mode = mateconf_client_get_bool(client, procman::mateconf::solaris_mode.c_str(), NULL); + mateconf_client_notify_add(client, procman::mateconf::solaris_mode.c_str(), solaris_mode_changed_cb, pd, NULL, NULL); + + pd->config.network_in_bits = mateconf_client_get_bool(client, procman::mateconf::network_in_bits.c_str(), NULL); + mateconf_client_notify_add(client, procman::mateconf::network_in_bits.c_str(), network_in_bits_changed_cb, pd, NULL, NULL); + + + pd->config.show_kill_warning = mateconf_client_get_bool (client, "/apps/procman/kill_dialog", + NULL); + mateconf_client_notify_add (client, "/apps/procman/kill_dialog", warning_changed_cb, + pd, NULL, NULL); + pd->config.update_interval = mateconf_client_get_int (client, "/apps/procman/update_interval", + NULL); + mateconf_client_notify_add (client, "/apps/procman/update_interval", timeouts_changed_cb, + pd, NULL, NULL); + pd->config.graph_update_interval = mateconf_client_get_int (client, + "/apps/procman/graph_update_interval", + NULL); + mateconf_client_notify_add (client, "/apps/procman/graph_update_interval", timeouts_changed_cb, + pd, NULL, NULL); + pd->config.disks_update_interval = mateconf_client_get_int (client, + "/apps/procman/disks_interval", + NULL); + mateconf_client_notify_add (client, "/apps/procman/disks_interval", timeouts_changed_cb, + pd, NULL, NULL); + + + /* /apps/procman/show_all_fs */ + pd->config.show_all_fs = mateconf_client_get_bool ( + client, "/apps/procman/show_all_fs", + NULL); + mateconf_client_notify_add + (client, "/apps/procman/show_all_fs", + show_all_fs_changed_cb, pd, NULL, NULL); + + + pd->config.whose_process = mateconf_client_get_int (client, "/apps/procman/view_as", NULL); + mateconf_client_notify_add (client, "/apps/procman/view_as", view_as_changed_cb, + pd, NULL, NULL); + pd->config.current_tab = mateconf_client_get_int (client, "/apps/procman/current_tab", NULL); + + for (int i = 0; i < GLIBTOP_NCPU; i++) { + gchar *key; + key = g_strdup_printf ("/apps/procman/cpu_color%d", i); + + color = mateconf_client_get_string (client, key, NULL); + if (!color) + color = g_strdup ("#f25915e815e8"); + mateconf_client_notify_add (client, key, + color_changed_cb, pd, NULL, NULL); + gdk_color_parse(color, &pd->config.cpu_color[i]); + g_free (color); + g_free (key); + } + color = mateconf_client_get_string (client, "/apps/procman/mem_color", NULL); + if (!color) + color = g_strdup ("#000000ff0082"); + mateconf_client_notify_add (client, "/apps/procman/mem_color", + color_changed_cb, pd, NULL, NULL); + gdk_color_parse(color, &pd->config.mem_color); + + g_free (color); + + color = mateconf_client_get_string (client, "/apps/procman/swap_color", NULL); + if (!color) + color = g_strdup ("#00b6000000ff"); + mateconf_client_notify_add (client, "/apps/procman/swap_color", + color_changed_cb, pd, NULL, NULL); + gdk_color_parse(color, &pd->config.swap_color); + g_free (color); + + color = mateconf_client_get_string (client, "/apps/procman/net_in_color", NULL); + if (!color) + color = g_strdup ("#000000f200f2"); + mateconf_client_notify_add (client, "/apps/procman/net_in_color", + color_changed_cb, pd, NULL, NULL); + gdk_color_parse(color, &pd->config.net_in_color); + g_free (color); + + color = mateconf_client_get_string (client, "/apps/procman/net_out_color", NULL); + if (!color) + color = g_strdup ("#00f2000000c1"); + mateconf_client_notify_add (client, "/apps/procman/net_out_color", + color_changed_cb, pd, NULL, NULL); + gdk_color_parse(color, &pd->config.net_out_color); + g_free (color); + + /* Sanity checks */ + swidth = gdk_screen_width (); + sheight = gdk_screen_height (); + pd->config.width = CLAMP (pd->config.width, 50, swidth); + pd->config.height = CLAMP (pd->config.height, 50, sheight); + pd->config.update_interval = MAX (pd->config.update_interval, 1000); + pd->config.graph_update_interval = MAX (pd->config.graph_update_interval, 250); + pd->config.disks_update_interval = MAX (pd->config.disks_update_interval, 1000); + pd->config.whose_process = CLAMP (pd->config.whose_process, 0, 2); + pd->config.current_tab = CLAMP(pd->config.current_tab, + PROCMAN_TAB_SYSINFO, + PROCMAN_TAB_DISKS); + + /* Determinie number of cpus since libgtop doesn't really tell you*/ + pd->config.num_cpus = 0; + glibtop_get_cpu (&cpu); + pd->frequency = cpu.frequency; + i=0; + while (i < GLIBTOP_NCPU && cpu.xcpu_total[i] != 0) { + pd->config.num_cpus ++; + i++; + } + if (pd->config.num_cpus == 0) + pd->config.num_cpus = 1; + + // delayed initialization as SmoothRefresh() needs ProcData + // i.e. we can't call ProcData::get_instance + pd->smooth_refresh = new SmoothRefresh(); + + return pd; + +} + +static void +procman_free_data (ProcData *procdata) +{ + + proctable_free_table (procdata); + delete procdata->smooth_refresh; +} + + +gboolean +procman_get_tree_state (MateConfClient *client, GtkWidget *tree, const gchar *prefix) +{ + GtkTreeModel *model; + GList *columns, *it; + gint sort_col; + GtkSortType order; + gchar *key; + + + g_assert(tree); + g_assert(prefix); + + if (!mateconf_client_dir_exists (client, prefix, NULL)) + return FALSE; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + + key = g_strdup_printf ("%s/sort_col", prefix); + sort_col = mateconf_client_get_int (client, key, NULL); + g_free (key); + + key = g_strdup_printf ("%s/sort_order", prefix); + order = static_cast<GtkSortType>(mateconf_client_get_int (client, key, NULL)); + g_free (key); + + if (sort_col != -1) + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), + sort_col, + order); + + columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree)); + + for(it = columns; it; it = it->next) + { + GtkTreeViewColumn *column; + MateConfValue *value = NULL; + gint width; + gboolean visible; + int id; + + column = static_cast<GtkTreeViewColumn*>(it->data); + id = gtk_tree_view_column_get_sort_column_id (column); + + key = g_strdup_printf ("%s/col_%d_width", prefix, id); + value = mateconf_client_get (client, key, NULL); + g_free (key); + + if (value != NULL) { + width = mateconf_value_get_int(value); + mateconf_value_free (value); + + key = g_strdup_printf ("%s/col_%d_visible", prefix, id); + visible = mateconf_client_get_bool (client, key, NULL); + g_free (key); + + column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), id); + if(!column) continue; + gtk_tree_view_column_set_visible (column, visible); + if (visible) { + /* ensure column is really visible */ + width = MAX(width, 10); + gtk_tree_view_column_set_fixed_width(column, width); + } + } + } + + if(g_str_has_suffix(prefix, "proctree") || g_str_has_suffix(prefix, "disktreenew")) + { + GSList *order; + char *key; + + key = g_strdup_printf("%s/columns_order", prefix); + order = mateconf_client_get_list(client, key, MATECONF_VALUE_INT, NULL); + proctable_set_columns_order(GTK_TREE_VIEW(tree), order); + + g_slist_free(order); + g_free(key); + } + + g_list_free(columns); + + return TRUE; +} + +void +procman_save_tree_state (MateConfClient *client, GtkWidget *tree, const gchar *prefix) +{ + GtkTreeModel *model; + GList *it, *columns; + gint sort_col; + GtkSortType order; + + g_assert(tree); + g_assert(prefix); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + if (gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model), &sort_col, + &order)) { + gchar *key; + + key = g_strdup_printf ("%s/sort_col", prefix); + mateconf_client_set_int (client, key, sort_col, NULL); + g_free (key); + + key = g_strdup_printf ("%s/sort_order", prefix); + mateconf_client_set_int (client, key, order, NULL); + g_free (key); + } + + columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree)); + + for(it = columns; it; it = it->next) + { + GtkTreeViewColumn *column; + gboolean visible; + gint width; + gchar *key; + int id; + + column = static_cast<GtkTreeViewColumn*>(it->data); + id = gtk_tree_view_column_get_sort_column_id (column); + visible = gtk_tree_view_column_get_visible (column); + width = gtk_tree_view_column_get_width (column); + + key = g_strdup_printf ("%s/col_%d_width", prefix, id); + mateconf_client_set_int (client, key, width, NULL); + g_free (key); + + key = g_strdup_printf ("%s/col_%d_visible", prefix, id); + mateconf_client_set_bool (client, key, visible, NULL); + g_free (key); + } + + if(g_str_has_suffix(prefix, "proctree") || g_str_has_suffix(prefix, "disktreenew")) + { + GSList *order; + char *key; + GError *error = NULL; + + key = g_strdup_printf("%s/columns_order", prefix); + order = proctable_get_columns_order(GTK_TREE_VIEW(tree)); + + if(!mateconf_client_set_list(client, key, MATECONF_VALUE_INT, order, &error)) + { + g_critical("Could not save MateConf key '%s' : %s", + key, + error->message); + g_error_free(error); + } + + g_slist_free(order); + g_free(key); + } + + g_list_free(columns); +} + +void +procman_save_config (ProcData *data) +{ + MateConfClient *client = data->client; + + + g_assert(data); + + procman_save_tree_state (data->client, data->tree, "/apps/procman/proctree"); + procman_save_tree_state (data->client, data->disk_list, "/apps/procman/disktreenew"); + + #if GTK_CHECK_VERSION(3, 0, 0) + data->config.width = gdk_window_get_width(gtk_widget_get_window(data->app)); + data->config.height = gdk_window_get_height(gtk_widget_get_window(data->app)); + #else + gint width, height; + + gdk_drawable_get_size(gtk_widget_get_window(data->app), &width, &height); + + data->config.width = width; + data->config.height = height; + #endif + + mateconf_client_set_int (client, "/apps/procman/width", data->config.width, NULL); + mateconf_client_set_int (client, "/apps/procman/height", data->config.height, NULL); + mateconf_client_set_int (client, "/apps/procman/current_tab", data->config.current_tab, NULL); + + mateconf_client_suggest_sync (client, NULL); + + + +} + +static guint32 +get_startup_timestamp () +{ + const gchar *startup_id_env; + gchar *startup_id = NULL; + gchar *time_str; + gulong retval = 0; + + /* we don't unset the env, since startup-notification + * may still need it */ + startup_id_env = g_getenv ("DESKTOP_STARTUP_ID"); + if (startup_id_env == NULL) + goto out; + + startup_id = g_strdup (startup_id_env); + + time_str = g_strrstr (startup_id, "_TIME"); + if (time_str == NULL) + goto out; + + /* Skip past the "_TIME" part */ + time_str += 5; + + retval = strtoul (time_str, NULL, 0); + + out: + g_free (startup_id); + + return retval; +} + + +static void +cb_server (const gchar *msg, gpointer user_data) +{ + GdkWindow *window; + ProcData *procdata; + guint32 timestamp = 0; + + window = gdk_get_default_root_window (); + + procdata = *(ProcData**)user_data; + g_assert (procdata != NULL); + + procman_debug("cb_server(%s)", msg); + if (msg != NULL && procman::SHOW_SYSTEM_TAB_CMD == msg) { + procman_debug("Changing to PROCMAN_TAB_SYSINFO via bacon message"); + gtk_notebook_set_current_page(GTK_NOTEBOOK(procdata->notebook), PROCMAN_TAB_SYSINFO); + cb_change_current_page(GTK_NOTEBOOK(procdata->notebook), PROCMAN_TAB_SYSINFO, procdata); + } else + timestamp = strtoul(msg, NULL, 0); + + if (timestamp == 0) + { + /* fall back to rountripping to X */ + timestamp = gdk_x11_get_server_time (window); + } + + gdk_x11_window_set_user_time (window, timestamp); + + gtk_window_present (GTK_WINDOW(procdata->app)); +} + + + + +static void +mount_changed(const Glib::RefPtr<Gio::Mount>&) +{ + cb_update_disks(ProcData::get_instance()); +} + + +static void +init_volume_monitor(ProcData *procdata) +{ + using namespace Gio; + using namespace Glib; + + RefPtr<VolumeMonitor> monitor = VolumeMonitor::get(); + + monitor->signal_mount_added().connect(sigc::ptr_fun(&mount_changed)); + monitor->signal_mount_changed().connect(sigc::ptr_fun(&mount_changed)); + monitor->signal_mount_removed().connect(sigc::ptr_fun(&mount_changed)); +} + + +namespace procman +{ + const std::string SHOW_SYSTEM_TAB_CMD("SHOWSYSTAB"); +} + + + +int +main (int argc, char *argv[]) +{ + guint32 startup_timestamp; + MateConfClient *client; + ProcData *procdata; + BaconMessageConnection *conn; + + bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + setlocale (LC_ALL, ""); + + startup_timestamp = get_startup_timestamp(); + + 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()); + } + + Gio::init(); + Gtk::Main kit(&argc, &argv); + procman_debug("post gtk_init"); + + conn = bacon_message_connection_new ("mate-system-monitor"); + if (!conn) g_error("Couldn't connect to mate-system-monitor"); + + if (bacon_message_connection_get_is_server (conn)) + { + bacon_message_connection_set_callback (conn, cb_server, &procdata); + } + else /* client */ + { + char *timestamp; + + timestamp = g_strdup_printf ("%" G_GUINT32_FORMAT, startup_timestamp); + + if (option_group.show_system_tab) + bacon_message_connection_send(conn, procman::SHOW_SYSTEM_TAB_CMD.c_str()); + + bacon_message_connection_send (conn, timestamp); + + gdk_notify_startup_complete (); + + g_free (timestamp); + bacon_message_connection_free (conn); + + exit (0); + } + + gtk_window_set_default_icon_name ("utilities-system-monitor"); + g_set_application_name(_("System Monitor")); + + mateconf_init (argc, argv, NULL); + + client = mateconf_client_get_default (); + mateconf_client_add_dir(client, "/apps/procman", MATECONF_CLIENT_PRELOAD_NONE, NULL); + + glibtop_init (); + + procman_debug("end init"); + + procdata = procman_data_new (client); + procdata->client = client; + + procman_debug("begin create_main_window"); + create_main_window (procdata); + procman_debug("end create_main_window"); + + // proctable_update_all (procdata); + + init_volume_monitor (procdata); + + g_assert(procdata->app); + + if (option_group.show_system_tab) { + procman_debug("Starting with PROCMAN_TAB_SYSINFO by commandline request"); + gtk_notebook_set_current_page(GTK_NOTEBOOK(procdata->notebook), PROCMAN_TAB_SYSINFO); + cb_change_current_page (GTK_NOTEBOOK(procdata->notebook), PROCMAN_TAB_SYSINFO, procdata); + } + + gtk_widget_show(procdata->app); + + procman_debug("begin gtk_main"); + kit.run(); + + procman_free_data (procdata); + + glibtop_close (); + + return 0; +} + diff --git a/src/procman.h b/src/procman.h new file mode 100644 index 0000000..76c998f --- /dev/null +++ b/src/procman.h @@ -0,0 +1,247 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ +#ifndef _PROCMAN_PROCMAN_H_ +#define _PROCMAN_PROCMAN_H_ + + +#include <glibmm/refptr.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <glib.h> +#include <gtk/gtk.h> +#include <mateconf/mateconf-client.h> +#include <glibtop/cpu.h> + +#include <time.h> +#include <sys/types.h> + +#include <map> +#include <string> + +struct ProcInfo; +struct ProcData; +struct LoadGraph; + +#include "smooth_refresh.h" +#include "prettytable.h" + +enum +{ + ALL_PROCESSES, + MY_PROCESSES, + ACTIVE_PROCESSES +}; + + +static const unsigned MIN_UPDATE_INTERVAL = 1 * 1000; +static const unsigned MAX_UPDATE_INTERVAL = 100 * 1000; + + +namespace procman +{ + extern const std::string SHOW_SYSTEM_TAB_CMD; +} + + + +enum ProcmanTab +{ + PROCMAN_TAB_SYSINFO, + PROCMAN_TAB_PROCESSES, + PROCMAN_TAB_RESOURCES, + PROCMAN_TAB_DISKS +}; + + +struct ProcConfig +{ + gint width; + gint height; + gboolean show_kill_warning; + gboolean show_tree; + gboolean show_all_fs; + int update_interval; + int graph_update_interval; + int disks_update_interval; + gint whose_process; + gint current_tab; + GdkColor cpu_color[GLIBTOP_NCPU]; + GdkColor mem_color; + GdkColor swap_color; + GdkColor net_in_color; + GdkColor net_out_color; + GdkColor bg_color; + GdkColor frame_color; + gint num_cpus; + bool solaris_mode; + bool network_in_bits; +}; + + + +struct MutableProcInfo +{ + MutableProcInfo() + : status(0) + { } + + std::string user; + + gchar wchan[40]; + + // 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; + + // wnck gives an unsigned long + gulong memxserver; + + gulong start_time; + guint64 cpu_time; + guint status; + guint pcpu; + gint nice; +}; + + +class ProcInfo + : public MutableProcInfo +{ + /* undefined */ ProcInfo& operator=(const ProcInfo&); + /* undefined */ ProcInfo(const ProcInfo&); + + typedef std::map<guint, std::string> UserMap; + /* cached username */ + static UserMap users; + + public: + + // 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; + typedef List::iterator Iterator; + + static List all; + + static ProcInfo* find(pid_t pid); + static Iterator begin() { return ProcInfo::all.begin(); } + static Iterator end() { return ProcInfo::all.end(); } + + + ProcInfo(pid_t pid); + ~ProcInfo(); + // adds one more ref to icon + void set_icon(Glib::RefPtr<Gdk::Pixbuf> icon); + void set_user(guint uid); + + GtkTreeIter node; + Glib::RefPtr<Gdk::Pixbuf> pixbuf; + gchar *tooltip; + gchar *name; + gchar *arguments; + + gchar *security_context; + + const guint pid; + guint ppid; + guint uid; + +// private: + // tracks cpu time per process keeps growing because if a + // ProcInfo is deleted this does not mean that the process is + // not going to be recreated on the next update. For example, + // if dependencies + (My or Active), the proclist is cleared + // on each update. This is a workaround + static std::map<pid_t, guint64> cpu_times; +}; + +struct ProcData +{ + // lazy initialization + static ProcData* get_instance(); + + GtkUIManager *uimanager; + GtkActionGroup *action_group; + GtkWidget *statusbar; + gint tip_message_cid; + GtkWidget *tree; + GtkWidget *loadavg; + GtkWidget *endprocessbutton; + GtkWidget *popup_menu; + GtkWidget *disk_list; + GtkWidget *notebook; + ProcConfig config; + LoadGraph *cpu_graph; + LoadGraph *mem_graph; + LoadGraph *net_graph; + gint cpu_label_fixed_width; + gint net_label_fixed_width; + ProcInfo *selected_process; + GtkTreeSelection *selection; + guint timeout; + guint disk_timeout; + + PrettyTable pretty_table; + + MateConfClient *client; + GtkWidget *app; + GtkUIManager *menu; + + unsigned frequency; + + SmoothRefresh *smooth_refresh; + + guint64 cpu_total_time; + guint64 cpu_total_time_last; + +private: + ProcData(); + /* undefined */ ProcData(const ProcData &); + /* undefined */ ProcData& operator=(const ProcData &); +}; + +void procman_save_config (ProcData *data); +void procman_save_tree_state (MateConfClient *client, GtkWidget *tree, const gchar *prefix); +gboolean procman_get_tree_state (MateConfClient *client, GtkWidget *tree, const gchar *prefix); + + + + + +struct ReniceArgs +{ + ProcData *procdata; + int nice_value; +}; + + +struct KillArgs +{ + ProcData *procdata; + int signal; +}; + +#endif /* _PROCMAN_PROCMAN_H_ */ diff --git a/src/procman_gksu.cpp b/src/procman_gksu.cpp new file mode 100644 index 0000000..d2737cc --- /dev/null +++ b/src/procman_gksu.cpp @@ -0,0 +1,54 @@ +#include <config.h> + +#include "procman.h" +#include "procman_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 procman_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/procman_gksu.h b/src/procman_gksu.h new file mode 100644 index 0000000..505220e --- /dev/null +++ b/src/procman_gksu.h @@ -0,0 +1,12 @@ +#ifndef H_MATE_SYSTEM_MONITOR_GKSU_H_1132171928 +#define H_MATE_SYSTEM_MONITOR_GKSU_H_1132171928 + +#include <glib.h> + +gboolean +procman_gksu_create_root_password_dialog(const char * command); + +gboolean +procman_has_gksu(void) G_GNUC_CONST; + +#endif /* H_MATE_SYSTEM_MONITOR_GKSU_H_1132171928 */ diff --git a/src/procman_matesu.cpp b/src/procman_matesu.cpp new file mode 100644 index 0000000..5bf6c59 --- /dev/null +++ b/src/procman_matesu.cpp @@ -0,0 +1,40 @@ +#include <config.h> + +#include <glib.h> + +#include "procman.h" +#include "procman_matesu.h" +#include "util.h" + +gboolean (*matesu_exec)(const char *commandline); + + +static void +load_matesu(void) +{ + static gboolean init; + + if (init) + return; + + init = TRUE; + + load_symbols("libmatesu.so.0", + "matesu_exec", &matesu_exec, + NULL); +} + + +gboolean +procman_matesu_create_root_password_dialog(const char *command) +{ + return matesu_exec(command); +} + + +gboolean +procman_has_matesu(void) +{ + load_matesu(); + return matesu_exec != NULL; +} diff --git a/src/procman_matesu.h b/src/procman_matesu.h new file mode 100644 index 0000000..099c756 --- /dev/null +++ b/src/procman_matesu.h @@ -0,0 +1,12 @@ +#ifndef H_MATE_SYSTEM_MONITOR_MATESU_H_1132171917 +#define H_MATE_SYSTEM_MONITOR_MATESU_H_1132171917 + +#include <glib/gtypes.h> + +gboolean +procman_matesu_create_root_password_dialog(const char * message); + +gboolean +procman_has_matesu(void) G_GNUC_CONST; + +#endif /* H_MATE_SYSTEM_MONITOR_MATESU_H_1132171917 */ diff --git a/src/proctable.cpp b/src/proctable.cpp new file mode 100644 index 0000000..7f192b2 --- /dev/null +++ b/src/proctable.cpp @@ -0,0 +1,964 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + + +#include <config.h> + + +#include <string.h> +#include <math.h> +#include <glib/gi18n.h> +#include <glib/gprintf.h> +#include <glibtop.h> +#include <glibtop/loadavg.h> +#include <glibtop/proclist.h> +#include <glibtop/procstate.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> + +#include "procman.h" +#include "selection.h" +#include "proctable.h" +#include "callbacks.h" +#include "prettytable.h" +#include "util.h" +#include "interface.h" +#include "selinux.h" + + +ProcInfo::UserMap ProcInfo::users; +ProcInfo::List ProcInfo::all; +std::map<pid_t, guint64> ProcInfo::cpu_times; + + +ProcInfo* ProcInfo::find(pid_t pid) +{ + Iterator it(ProcInfo::all.find(pid)); + return (it == ProcInfo::all.end() ? NULL : it->second); +} + + + + +static void +set_proctree_reorderable(ProcData *procdata) +{ + GList *columns, *col; + GtkTreeView *proctree; + + proctree = GTK_TREE_VIEW(procdata->tree); + + columns = gtk_tree_view_get_columns (proctree); + + for(col = columns; col; col = col->next) + gtk_tree_view_column_set_reorderable(static_cast<GtkTreeViewColumn*>(col->data), TRUE); + + g_list_free(columns); +} + + +static void +cb_columns_changed(GtkTreeView *treeview, gpointer user_data) +{ + ProcData * const procdata = static_cast<ProcData*>(user_data); + + procman_save_tree_state(procdata->client, + GTK_WIDGET(treeview), + "/apps/procman/proctree"); +} + + +static GtkTreeViewColumn* +my_gtk_tree_view_get_column_with_sort_column_id(GtkTreeView *treeview, int id) +{ + GList *columns, *it; + GtkTreeViewColumn *col = NULL; + + columns = gtk_tree_view_get_columns(treeview); + + for(it = columns; it; it = it->next) + { + if(gtk_tree_view_column_get_sort_column_id(static_cast<GtkTreeViewColumn*>(it->data)) == id) + { + col = static_cast<GtkTreeViewColumn*>(it->data); + break; + } + } + + g_list_free(columns); + + return col; +} + + +void +proctable_set_columns_order(GtkTreeView *treeview, GSList *order) +{ + GtkTreeViewColumn* last = NULL; + GSList *it; + + for(it = order; it; it = it->next) + { + int id; + GtkTreeViewColumn *cur; + + id = GPOINTER_TO_INT(it->data); + + g_assert(id >= 0 && id < NUM_COLUMNS); + + cur = my_gtk_tree_view_get_column_with_sort_column_id(treeview, id); + + if(cur && cur != last) + { + gtk_tree_view_move_column_after(treeview, cur, last); + last = cur; + } + } +} + + + +GSList* +proctable_get_columns_order(GtkTreeView *treeview) +{ + GList *columns, *col; + GSList *order = NULL; + + columns = gtk_tree_view_get_columns(treeview); + + for(col = columns; col; col = col->next) + { + int id; + + id = gtk_tree_view_column_get_sort_column_id(static_cast<GtkTreeViewColumn*>(col->data)); + order = g_slist_prepend(order, GINT_TO_POINTER(id)); + } + + g_list_free(columns); + + order = g_slist_reverse(order); + + return order; +} + + +static gboolean +search_equal_func(GtkTreeModel *model, + gint column, + const gchar *key, + GtkTreeIter *iter, + gpointer search_data) +{ + char* name; + char* user; + gboolean found; + + gtk_tree_model_get(model, iter, + COL_NAME, &name, + COL_USER, &user, + -1); + + found = !((name && strstr(name, key)) + || (user && strstr(user, key))); + + g_free(name); + g_free(user); + + return found; +} + + + +GtkWidget * +proctable_new (ProcData * const procdata) +{ + GtkWidget *proctree; + GtkWidget *scrolled; + GtkTreeStore *model; + 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: wchan, see ps(1) or top(1) */ + N_("Waiting Channel"), + NULL, + "POINTER" + }; + + gint i; + + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + 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 */ + GDK_TYPE_PIXBUF, /* Icon */ + G_TYPE_POINTER, /* ProcInfo */ + G_TYPE_STRING /* Sexy tooltip */ + ); + + proctree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (proctree), COL_TOOLTIP); + g_object_set(G_OBJECT(proctree), + "show-expanders", procdata->config.show_tree, + NULL); + gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (proctree), + search_equal_func, + NULL, + NULL); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (proctree), TRUE); + g_object_unref (G_OBJECT (model)); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (proctree)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + + 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_append_column (GTK_TREE_VIEW (proctree), column); + gtk_tree_view_set_expander_column (GTK_TREE_VIEW (proctree), column); + + + for (i = COL_USER; i <= COL_WCHAN; 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(proctree), col); + + // type + switch (i) { + case COL_MEMXSERVER: + gtk_tree_view_column_set_cell_data_func(col, cell, + &procman::size_cell_data_func, + GUINT_TO_POINTER(i), + NULL); + break; + + case COL_VMSIZE: + case COL_MEMRES: + case COL_MEMSHARED: + case COL_MEM: + case COL_MEMWRITABLE: + 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; + + default: + gtk_tree_view_column_set_attributes(col, cell, "text", i, NULL); + break; + } + + // xaling + switch(i) + { + case COL_VMSIZE: + case COL_MEMRES: + case COL_MEMWRITABLE: + case COL_MEMSHARED: + case COL_MEMXSERVER: + case COL_CPU: + case COL_NICE: + case COL_PID: + case COL_CPU_TIME: + case COL_MEM: + g_object_set(G_OBJECT(cell), "xalign", 1.0f, NULL); + break; + } + + // sizing + switch (i) { + case COL_ARGS: + gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_min_width(col, 150); + break; + default: + gtk_tree_view_column_set_min_width(column, 20); + break; + } + } + + gtk_container_add (GTK_CONTAINER (scrolled), proctree); + + procdata->tree = proctree; + + set_proctree_reorderable(procdata); + + procman_get_tree_state (procdata->client, proctree, "/apps/procman/proctree"); + + /* Override column settings by hiding this column if it's meaningless: */ + if (!can_show_security_context_column ()) { + GtkTreeViewColumn *column; + column = gtk_tree_view_get_column (GTK_TREE_VIEW (proctree), COL_SECURITYCONTEXT); + gtk_tree_view_column_set_visible (column, FALSE); + } + + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (proctree))), + "changed", + G_CALLBACK (cb_row_selected), procdata); + g_signal_connect (G_OBJECT (proctree), "popup_menu", + G_CALLBACK (cb_tree_popup_menu), procdata); + g_signal_connect (G_OBJECT (proctree), "button_press_event", + G_CALLBACK (cb_tree_button_pressed), procdata); + + g_signal_connect (G_OBJECT(proctree), "columns-changed", + G_CALLBACK(cb_columns_changed), procdata); + + return scrolled; +} + + +ProcInfo::~ProcInfo() +{ + g_free(this->name); + g_free(this->tooltip); + g_free(this->arguments); + g_free(this->security_context); +} + + +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 = basename; + return; + } + + g_free(basename); + } + } + + info->name = g_strdup (cmd); +} + + + +void +ProcInfo::set_user(guint uid) +{ + if (G_LIKELY(this->uid == uid)) + return; + + this->uid = uid; + + typedef std::pair<ProcInfo::UserMap::iterator, bool> Pair; + ProcInfo::UserMap::value_type hint(uid, ""); + Pair p(ProcInfo::users.insert(hint)); + + // 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; + } + } + + this->user = p.first->second; +} + + + +static void get_process_memory_writable(ProcInfo *info) +{ + glibtop_proc_map buf; + glibtop_map_entry *maps; + + maps = glibtop_get_proc_map(&buf, info->pid); + + gulong memwritable = 0; + const unsigned number = buf.number; + + for (unsigned i = 0; i < number; ++i) { +#ifdef __linux__ + memwritable += maps[i].private_dirty; +#else + if (maps[i].perm & GLIBTOP_MAP_PERM_WRITE) + memwritable += maps[i].size; +#endif + } + + info->memwritable = memwritable; + + g_free(maps); +} + + +static void +get_process_memory_info(ProcInfo *info) +{ + glibtop_proc_mem procmem; + WnckResourceUsage xresources; + + wnck_pid_read_resource_usage (gdk_screen_get_display (gdk_screen_get_default ()), + info->pid, + &xresources); + + glibtop_get_proc_mem(&procmem, info->pid); + + info->vmsize = procmem.vsize; + info->memres = procmem.resident; + info->memshared = procmem.share; + + info->memxserver = xresources.total_bytes_estimate; + + get_process_memory_writable(info); + + // fake the smart memory column if writable is not available + info->mem = info->memxserver + (info->memwritable ? info->memwritable : info->memres); +} + + + +static void +update_info_mutable_cols(ProcInfo *info) +{ + ProcData * const procdata = ProcData::get_instance(); + GtkTreeModel *model; + model = gtk_tree_view_get_model(GTK_TREE_VIEW(procdata->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_MEMWRITABLE, info->memwritable); + tree_store_update(model, &info->node, COL_MEMSHARED, info->memshared); + tree_store_update(model, &info->node, COL_MEMXSERVER, info->memxserver); + 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_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); +} + + + +static void +insert_info_to_tree (ProcInfo *info, ProcData *procdata, bool forced = false) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (procdata->tree)); + + if (procdata->config.show_tree) { + + ProcInfo *parent = 0; + + if (not forced) + parent = ProcInfo::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); + + if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(procdata->tree), parent_node)) + gtk_tree_view_expand_row(GTK_TREE_VIEW(procdata->tree), parent_node, FALSE); + 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, + COL_ARGS, info->arguments, + COL_TOOLTIP, info->tooltip, + COL_PID, info->pid, + COL_SECURITYCONTEXT, info->security_context, + -1); + + procdata->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 (ProcData *procdata, GtkTreeModel *model, + ProcInfo *current, List &orphans, unsigned lvl = 0) +{ + GtkTreeIter child_node; + + if (std::find(orphans.begin(), orphans.end(), current) != orphans.end()) { + procman_debug("[%u] %d already removed from tree", lvl, int(current->pid)); + return; + } + + procman_debug("[%u] pid %d, %d children", lvl, int(current->pid), + gtk_tree_model_iter_n_children(model, ¤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(procdata, model, child, orphans, lvl + 1); + } + + g_assert(not gtk_tree_model_iter_has_child(model, ¤t->node)); + + if (procdata->selected_process == current) + procdata->selected_process = NULL; + + orphans.push_back(current); + gtk_tree_store_remove(GTK_TREE_STORE(model), ¤t->node); + procman::poison(current->node, 0x69); +} + + + +static void +update_info (ProcData *procdata, ProcInfo *info) +{ + glibtop_proc_state procstate; + glibtop_proc_uid procuid; + glibtop_proc_time proctime; + glibtop_proc_kernel prockernel; + + glibtop_get_proc_kernel(&prockernel, info->pid); + g_strlcpy(info->wchan, prockernel.wchan, sizeof info->wchan); + + 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); + + get_process_memory_info(info); + + info->set_user(procstate.uid); + + info->pcpu = (proctime.rtime - info->cpu_time) * 100 / procdata->cpu_total_time; + info->pcpu = MIN(info->pcpu, 100); + + if (not procdata->config.solaris_mode) + info->pcpu *= procdata->config.num_cpus; + + ProcInfo::cpu_times[info->pid] = info->cpu_time = proctime.rtime; + info->nice = procuid.nice; + info->ppid = procuid.ppid; +} + + +ProcInfo::ProcInfo(pid_t pid) + : tooltip(NULL), + name(NULL), + arguments(NULL), + security_context(NULL), + pid(pid), + 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 = g_markup_escape_text(tooltip.c_str(), -1); + + info->arguments = g_strescape(tooltip.c_str(), "\\\""); + g_strfreev(arguments); + + guint64 cpu_time = proctime.rtime; + std::map<pid_t, guint64>::iterator it(ProcInfo::cpu_times.find(pid)); + if (it != ProcInfo::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); +} + + + + +static void +refresh_list (ProcData *procdata, const pid_t* pid_list, const guint n) +{ + typedef std::list<ProcInfo*> ProcList; + ProcList addition; + + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (procdata->tree)); + guint i; + + // Add or update processes in the process list + for(i = 0; i < n; ++i) { + ProcInfo *info = ProcInfo::find(pid_list[i]); + + if (!info) { + info = new ProcInfo(pid_list[i]); + ProcInfo::all[info->pid] = info; + addition.push_back(info); + } + + update_info (procdata, 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); + + ProcInfo::Iterator it(ProcInfo::begin()); + + while (it != ProcInfo::end()) { + ProcInfo * const info = it->second; + ProcInfo::Iterator next(it); + ++next; + + if (pids.find(info->pid) == pids.end()) { + procman_debug("ripping %d", info->pid); + remove_info_from_tree(procdata, model, info, addition); + addition.remove(info); + ProcInfo::all.erase(it); + delete info; + } + + it = next; + } + + // INVARIANT + // pid_list == ProcInfo::all + addition + + + if (procdata->config.show_tree) { + + // 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 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_list (ProcData * const procdata) + + + if ((*it)->ppid == 0 or in_tree.find((*it)->ppid) != in_tree.end()) { + insert_info_to_tree(*it, procdata); + in_tree.insert((*it)->pid); + it = addition.erase(it); + continue; + } + + ProcInfo *parent = ProcInfo::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, procdata, true); + in_tree.insert((*it)->pid); + it = addition.erase(it); + continue; + } + + ++it; + } + } + } + else { + // don't care of the tree + for (ProcList::iterator it(addition.begin()); it != addition.end(); ++it) + insert_info_to_tree(*it, procdata); + } + + + for (ProcInfo::Iterator it(ProcInfo::begin()); it != ProcInfo::end(); ++it) + update_info_mutable_cols(it->second); +} + + +void +proctable_update_list (ProcData * const procdata) +{ + pid_t* pid_list; + glibtop_proclist proclist; + glibtop_cpu cpu; + gint which, arg; + procman::SelectionMemento selection; + + switch (procdata->config.whose_process) { + case ALL_PROCESSES: + which = GLIBTOP_KERN_PROC_ALL; + arg = 0; + break; + + case ACTIVE_PROCESSES: + which = GLIBTOP_KERN_PROC_ALL | GLIBTOP_EXCLUDE_IDLE; + arg = 0; + if (procdata->config.show_tree) + { + selection.save(procdata->tree); + proctable_clear_tree(procdata); + } + break; + + default: + which = GLIBTOP_KERN_PROC_UID; + arg = getuid (); + if (procdata->config.show_tree) + { + selection.save(procdata->tree); + proctable_clear_tree(procdata); + } + break; + } + + 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); + procdata->cpu_total_time = MAX(cpu.total - procdata->cpu_total_time_last, 1); + procdata->cpu_total_time_last = cpu.total; + + refresh_list (procdata, pid_list, proclist.number); + + selection.restore(procdata->tree); + + g_free (pid_list); + + /* proclist.number == g_list_length(procdata->info) == g_hash_table_size(procdata->pids) */ +} + + +void +proctable_update_all (ProcData * const procdata) +{ + char* string; + + string = make_loadavg_string(); + gtk_label_set_text (GTK_LABEL(procdata->loadavg), string); + g_free (string); + + proctable_update_list (procdata); +} + + +void +proctable_clear_tree (ProcData * const procdata) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (procdata->tree)); + + gtk_tree_store_clear (GTK_TREE_STORE (model)); + + proctable_free_table (procdata); + + update_sensitivity(procdata); +} + + +void +proctable_free_table (ProcData * const procdata) +{ + for (ProcInfo::Iterator it(ProcInfo::begin()); it != ProcInfo::end(); ++it) + delete it->second; + + ProcInfo::all.clear(); +} + + + +char* +make_loadavg_string(void) +{ + glibtop_loadavg buf; + + glibtop_get_loadavg(&buf); + + return g_strdup_printf( + _("Load averages for the last 1, 5, 15 minutes: " + "%0.2f, %0.2f, %0.2f"), + buf.loadavg[0], + buf.loadavg[1], + buf.loadavg[2]); +} + + + +void +ProcInfo::set_icon(Glib::RefPtr<Gdk::Pixbuf> icon) +{ + this->pixbuf = icon; + + GtkTreeModel *model; + model = gtk_tree_view_get_model(GTK_TREE_VIEW(ProcData::get_instance()->tree)); + gtk_tree_store_set(GTK_TREE_STORE(model), &this->node, + COL_PIXBUF, (this->pixbuf ? this->pixbuf->gobj() : NULL), + -1); +} diff --git a/src/proctable.h b/src/proctable.h new file mode 100644 index 0000000..2cd1def --- /dev/null +++ b/src/proctable.h @@ -0,0 +1,65 @@ +/* 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _PROCMAN_PROCTABLE_H_ +#define _PROCMAN_PROCTABLE_H_ + +#include <glib.h> +#include <gtk/gtk.h> +#include "procman.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_PIXBUF, + COL_POINTER, + COL_TOOLTIP, + NUM_COLUMNS +}; + + +GtkWidget* proctable_new (ProcData *data); +void proctable_update_table (ProcData *data); +void proctable_update_list (ProcData *data); +void proctable_update_all (ProcData *data); +void proctable_clear_tree (ProcData *data); +void proctable_free_table (ProcData *data); + +GSList* proctable_get_columns_order(GtkTreeView *treeview); +void proctable_set_columns_order(GtkTreeView *treeview, GSList *order); + +char* make_loadavg_string(void); + +#endif /* _PROCMAN_PROCTABLE_H_ */ diff --git a/src/selection.cpp b/src/selection.cpp new file mode 100644 index 0000000..6f293a6 --- /dev/null +++ b/src/selection.cpp @@ -0,0 +1,43 @@ +#include <config.h> + +#include "selection.h" +#include "proctable.h" +#include "util.h" + +namespace procman +{ + void SelectionMemento::add_to_selected(GtkTreeModel* model, GtkTreePath*, GtkTreeIter* iter, gpointer data) + { + guint pid = 0; + gtk_tree_model_get(model, iter, COL_PID, &pid, -1); + if (pid) + static_cast<SelectionMemento*>(data)->pids.push_back(pid); + } + + + void SelectionMemento::save(GtkWidget* tree) + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); + gtk_tree_selection_selected_foreach(selection, &SelectionMemento::add_to_selected, this); + } + + + void SelectionMemento::restore(GtkWidget* tree) + { + if (not this->pids.empty()) + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); + typedef std::vector<pid_t>::iterator iterator; + for (iterator it(this->pids.begin()); it != this->pids.end(); ++it) + { + if (ProcInfo* proc = ProcInfo::find(*it)) + { + gtk_tree_selection_select_iter(selection, &proc->node); + procman_debug("Re-selected process %u", unsigned(*it)); + } + else + procman_debug("Could not find process %u, cannot re-select it", unsigned(*it)); + } + } + } +} diff --git a/src/selection.h b/src/selection.h new file mode 100644 index 0000000..1dd3470 --- /dev/null +++ b/src/selection.h @@ -0,0 +1,21 @@ +#ifndef H_MATE_SYSTEM_MONITOR_SELECTION_H_1183113337 +#define H_MATE_SYSTEM_MONITOR_SELECTION_H_1183113337 + +#include <sys/types.h> +#include <gtk/gtk.h> +#include <vector> + +namespace procman +{ + class SelectionMemento + { + std::vector<pid_t> pids; + static void add_to_selected(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data); + + public: + void save(GtkWidget* tree); + void restore(GtkWidget* tree); + }; +} + +#endif /* H_MATE_SYSTEM_MONITOR_SELECTION_H_1183113337 */ diff --git a/src/selinux.cpp b/src/selinux.cpp new file mode 100644 index 0000000..6bfee5a --- /dev/null +++ b/src/selinux.cpp @@ -0,0 +1,65 @@ +#include <config.h> + +#include <glib.h> + +#include "selinux.h" +#include "procman.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: + g_warning("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..be79ce9 --- /dev/null +++ b/src/selinux.h @@ -0,0 +1,14 @@ +#ifndef PROCMAN_SELINUX_H_20050525 +#define PROCMAN_SELINUX_H_20050525 + +#include <glib.h> + +#include "procman.h" + +void +get_process_selinux_context (ProcInfo *info); + +gboolean +can_show_security_context_column (void) G_GNUC_CONST; + +#endif /* PROCMAN_SELINUX_H_20050525 */ diff --git a/src/smooth_refresh.cpp b/src/smooth_refresh.cpp new file mode 100644 index 0000000..f21c023 --- /dev/null +++ b/src/smooth_refresh.cpp @@ -0,0 +1,173 @@ +#include <config.h> + +#include <sys/types.h> +#include <unistd.h> + +#include <glib.h> +#include <mateconf/mateconf-client.h> +#include <glibtop.h> +#include <glibtop/proctime.h> +#include <glibtop/cpu.h> + +#include <algorithm> + +#include "smooth_refresh.h" +#include "procman.h" +#include "util.h" + + +const string SmoothRefresh::KEY("/apps/procman/smooth_refresh"); +const bool SmoothRefresh::KEY_DEFAULT_VALUE(true); + + + +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; + } + + usage = CLAMP(usage, 0, 100); + + this->last_total_time = cpu.total; + this->last_cpu_time = proctime.rtime; + + return usage; +} + + + +void SmoothRefresh::status_changed(MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + static_cast<SmoothRefresh*>(user_data)->load_mateconf_value(mateconf_entry_get_value(entry)); +} + +void SmoothRefresh::load_mateconf_value(MateConfValue* value) +{ + bool own_value = false; + + if (not value) { + value = mateconf_client_get(mateconf_client_get_default(), KEY.c_str(), NULL); + } + + this->active = value ? mateconf_value_get_bool(value) : KEY_DEFAULT_VALUE; + + if (this->active) + procman_debug("smooth_refresh is enabled"); + + if (own_value and value) + mateconf_value_free(value); +} + + +SmoothRefresh::SmoothRefresh() +{ + this->connection = mateconf_client_notify_add(mateconf_client_get_default(), + KEY.c_str(), + status_changed, + this, + NULL, + NULL); + + this->reset(); + this->load_mateconf_value(); +} + + + +void SmoothRefresh::reset() +{ + glibtop_cpu cpu; + glibtop_proc_time proctime; + + glibtop_get_cpu(&cpu); + glibtop_get_proc_time(&proctime, getpid()); + + this->interval = ProcData::get_instance()->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) + mateconf_client_notify_remove(mateconf_client_get_default(), + this->connection); +} + + + +bool +SmoothRefresh::get(guint &new_interval) +{ + const unsigned config_interval = ProcData::get_instance()->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..29a503f --- /dev/null +++ b/src/smooth_refresh.h @@ -0,0 +1,104 @@ +#ifndef _PROCMAN_SMOOTH_REFRESH +#define _PROCMAN_SMOOTH_REFRESH + +#include <glib.h> +#include <mateconf/mateconf-client.h> + +#include <string> + +using std::string; + + + +class SmoothRefresh +{ +public: + + /* + smooth_refresh_new + + @config_interval : pointer to config_interval so we can observe + config_interval changes. + + @return : initialized SmoothRefresh + */ + SmoothRefresh(); + + ~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(); + + static void status_changed(MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data); + + void load_mateconf_value(MateConfValue* value = NULL); + + /* + 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% + */ + + bool active; + guint connection; + guint interval; + unsigned last_pcpu; + guint64 last_total_time; + guint64 last_cpu_time; +}; + + +#endif /* _PROCMAN_SMOOTH_REFRESH */ diff --git a/src/sysinfo.cpp b/src/sysinfo.cpp new file mode 100644 index 0000000..9ddf31c --- /dev/null +++ b/src/sysinfo.cpp @@ -0,0 +1,676 @@ +#include <config.h> + +#include <glib.h> +#include <gtk/gtk.h> +#include <glibmm.h> +#include <glib/gi18n.h> + +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> + +#include <glibtop/fsusage.h> +#include <glibtop/mountlist.h> +#include <glibtop/mem.h> +#include <glibtop/sysinfo.h> + +#include <unistd.h> +#include <netdb.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <math.h> +#include <errno.h> + +#include <string> +#include <vector> +#include <fstream> +#include <sstream> +#include <sys/utsname.h> + +#include "sysinfo.h" +#include "procman.h" +#include "util.h" + + +using std::string; +using std::vector; + + +namespace { + + + class SysInfo + { + public: + string hostname; + string distro_name; + string distro_release; + string kernel; + string mate_version; + guint64 memory_bytes; + guint64 free_space_bytes; + + guint n_processors; + vector<string> processors; + + + SysInfo() + { + this->load_processors_info(); + this->load_memory_info(); + this->load_disk_info(); + this->load_uname_info(); + this->load_mate_version(); + } + + virtual ~SysInfo() + { } + + virtual void set_distro_labels(GtkWidget* name, GtkWidget* release) + { + g_object_set(G_OBJECT(name), + "label", + ("<big><big><b>" + this->distro_name + "</b></big></big>").c_str(), + NULL); + + + char* markup = g_strdup_printf(_("Release %s"), this->distro_release.c_str()); + + g_object_set(G_OBJECT(release), + "label", + markup, + NULL); + + g_free(markup); + } + + static string system() + { + return uname().sysname; + } + + private: + + void load_memory_info() + { + glibtop_mem mem; + + glibtop_get_mem(&mem); + this->memory_bytes = mem.total; + } + + + void load_processors_info() + { + const glibtop_sysinfo *info = glibtop_get_sysinfo(); + + for (guint i = 0; i != info->ncpu; ++i) { + const char * const keys[] = { "model name", "cpu" }; + gchar *model = 0; + + for (guint j = 0; !model && j != G_N_ELEMENTS(keys); ++j) + model = static_cast<char*>(g_hash_table_lookup(info->cpuinfo[i].values, + keys[j])); + + if (!model) + model = _("Unknown CPU model"); + + this->processors.push_back(model); + } + } + + + + void load_disk_info() + { + glibtop_mountentry *entries; + glibtop_mountlist mountlist; + + entries = glibtop_get_mountlist(&mountlist, 0); + + this->free_space_bytes = 0; + + for (guint i = 0; i != mountlist.number; ++i) { + + if (string(entries[i].devname).find("/dev/") != 0) + continue; + + if (string(entries[i].mountdir).find("/media/") == 0) + continue; + + glibtop_fsusage usage; + glibtop_get_fsusage(&usage, entries[i].mountdir); + this->free_space_bytes += usage.bavail * usage.block_size; + } + + g_free(entries); + } + + static const struct utsname & uname() + { + static struct utsname name; + + if (!name.sysname[0]) { + ::uname(&name); + } + + return name; + } + + void load_uname_info() + { + this->hostname = uname().nodename; + this->kernel = string(uname().sysname) + ' ' + uname().release; + } + + + void load_mate_version() + { + xmlDocPtr document; + xmlXPathContextPtr context; + const string nodes[3] = { "string(/mate-version/platform)", + "string(/mate-version/minor)", + "string(/mate-version/micro)" }; + string values[3]; + + if (not (document = xmlParseFile(DATADIR "/mate-about/mate-version.xml"))) + return; + + if (not (context = xmlXPathNewContext(document))) + return; + + for (size_t i = 0; i != 3; ++i) + { + xmlXPathObjectPtr xpath; + xpath = xmlXPathEvalExpression(BAD_CAST nodes[i].c_str(), context); + + if (xpath and xpath->type == XPATH_STRING) + values[i] = reinterpret_cast<const char*>(xpath->stringval); + + xmlXPathFreeObject(xpath); + } + + xmlXPathFreeContext(context); + xmlFreeDoc(document); + + this->mate_version = values[0] + '.' + values[1] + '.' + values[2]; + } + }; + + + + class SolarisSysInfo + : public SysInfo + { + public: + SolarisSysInfo() + { + this->load_solaris_info(); + } + + private: + void load_solaris_info() + { + this->distro_name = "Solaris"; + + std::ifstream input("/etc/release"); + + if (input) + std::getline(input, this->distro_release); + } + }; + + + class LSBSysInfo + : public SysInfo + { + public: + LSBSysInfo() + : re(Glib::Regex::create("^.+?:\\s*(.+)\\s*$")) + { + // start(); + } + + virtual void set_distro_labels(GtkWidget* name, GtkWidget* release) + { + this->name = name; + this->release = release; + + this->start(); + } + + + private: + + sigc::connection child_watch; + int lsb_fd; + GtkWidget* name; + GtkWidget* release; + + void strip_description(string &s) const + { + const GRegexMatchFlags flags = static_cast<GRegexMatchFlags>(0); + GMatchInfo* info = 0; + + if (g_regex_match(this->re->gobj(), s.c_str(), flags, &info)) { + s = make_string(g_match_info_fetch(info, 1)); + g_match_info_free(info); + } + } + + std::istream& get_value(std::istream &is, string &s) const + { + if (std::getline(is, s)) + this->strip_description(s); + return is; + } + + + void read_lsb(Glib::Pid pid, int status) + { + this->child_watch.disconnect(); + + if (!WIFEXITED(status) or WEXITSTATUS(status) != 0) { + g_error("Child %d failed with status %d", int(pid), status); + return; + } + + Glib::RefPtr<Glib::IOChannel> channel = Glib::IOChannel::create_from_fd(this->lsb_fd); + Glib::ustring content; + + while (channel->read_to_end(content) == Glib::IO_STATUS_AGAIN) + ; + + channel->close(); + Glib::spawn_close_pid(pid); + + procman_debug("lsb_release output = '%s'", content.c_str()); + + string release, codename; + std::istringstream input(content); + + this->get_value(input, this->distro_name) + and this->get_value(input, release) + and this->get_value(input, codename); + + this->distro_release = release; + if (codename != "" && codename != "n/a") + this->distro_release += " (" + codename + ')'; + + this->SysInfo::set_distro_labels(this->name, this->release); + } + + + void start() + { + std::vector<string> argv(2); + argv[0] = "lsb_release"; + argv[1] = "-irc"; + + Glib::SpawnFlags flags = Glib::SPAWN_DO_NOT_REAP_CHILD + | Glib::SPAWN_SEARCH_PATH + | Glib::SPAWN_STDERR_TO_DEV_NULL; + + Glib::Pid child; + + try { + Glib::spawn_async_with_pipes("/", // wd + argv, + flags, + sigc::slot<void>(), // child setup + &child, + 0, // stdin + &this->lsb_fd); // stdout + } catch (Glib::SpawnError &e) { + g_error("g_spawn_async_with_pipes error: %s", e.what().c_str()); + return; + } + + sigc::slot<void,GPid, int> slot = sigc::mem_fun(this, &LSBSysInfo::read_lsb); + this->child_watch = Glib::signal_child_watch().connect(slot, child); + } + + + void sync_lsb_release() + { + char *out= 0; + GError *error = 0; + int status; + + if (g_spawn_command_line_sync("lsb_release -irc", + &out, + 0, + &status, + &error)) { + string release, codename; + if (!error and WIFEXITED(status) and WEXITSTATUS(status) == 0) { + std::istringstream input(out); + this->get_value(input, this->distro_name) + and this->get_value(input, release) + and this->get_value(input, codename); + this->distro_release = release; + if (codename != "" && codename != "n/a") + this->distro_release += " (" + codename + ')'; + } + } + + if (error) + g_error_free(error); + + g_free(out); + } + + private: + Glib::RefPtr<Glib::Regex> re; + }; + + + class NetBSDSysInfo + : public SysInfo + { + public: + NetBSDSysInfo() + { + this->load_netbsd_info(); + } + + private: + void load_netbsd_info() + { + this->distro_name = "NetBSD"; + + std::ifstream input("/etc/release"); + + if (input) + std::getline(input, this->distro_release); + } + }; + + + SysInfo* get_sysinfo() + { + if (char *p = g_find_program_in_path("lsb_release")) { + g_free(p); + return new LSBSysInfo; + } + else if (SysInfo::system() == "SunOS") { + return new SolarisSysInfo; + } + else if (SysInfo::system() == "NetBSD") { + return new NetBSDSysInfo; + } + + return new SysInfo; + } +} + + +#define X_PAD 5 +#define Y_PAD 12 +#define LOGO_W 92 +#define LOGO_H 351 +#define RADIUS 5 + +static gboolean +sysinfo_logo_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer data_ptr) +{ + GtkAllocation allocation; + GtkStyle *style; + cairo_t *cr; + cairo_pattern_t *cp; + + cr = gdk_cairo_create(gtk_widget_get_window(widget)); + + gtk_widget_get_allocation (widget, &allocation); + cairo_translate(cr, allocation.x, allocation.y); + + cairo_move_to(cr, X_PAD + RADIUS, Y_PAD); + cairo_line_to(cr, X_PAD + LOGO_W - RADIUS, Y_PAD); + cairo_arc(cr, X_PAD + LOGO_W - RADIUS, Y_PAD + RADIUS, RADIUS, -0.5 * M_PI, 0); + cairo_line_to(cr, X_PAD + LOGO_W, Y_PAD + LOGO_H - RADIUS); + cairo_arc(cr, X_PAD + LOGO_W - RADIUS, Y_PAD + LOGO_H - RADIUS, RADIUS, 0, 0.5 * M_PI); + cairo_line_to(cr, X_PAD + RADIUS, Y_PAD + LOGO_H); + cairo_arc(cr, X_PAD + RADIUS, Y_PAD + LOGO_H - RADIUS, RADIUS, 0.5 * M_PI, -1.0 * M_PI); + cairo_line_to(cr, X_PAD, Y_PAD + RADIUS); + cairo_arc(cr, X_PAD + RADIUS, Y_PAD + RADIUS, RADIUS, -1.0 * M_PI, -0.5 * M_PI); + + cp = cairo_pattern_create_linear(0, Y_PAD, 0, Y_PAD + LOGO_H); + style = gtk_widget_get_style (widget); + cairo_pattern_add_color_stop_rgba(cp, 0.0, + style->base[GTK_STATE_SELECTED].red / 65535.0, + style->base[GTK_STATE_SELECTED].green / 65535.0, + style->base[GTK_STATE_SELECTED].blue / 65535.0, + 1.0); + cairo_pattern_add_color_stop_rgba(cp, 1.0, + style->base[GTK_STATE_SELECTED].red / 65535.0, + style->base[GTK_STATE_SELECTED].green / 65535.0, + style->base[GTK_STATE_SELECTED].blue / 65535.0, + 0.0); + cairo_set_source(cr, cp); + cairo_fill(cr); + + cairo_pattern_destroy(cp); + cairo_destroy(cr); + + return FALSE; +} + +static GtkWidget* +add_section(GtkBox *vbox , const char * title, int num_row, int num_col, GtkWidget **out_frame) +{ + GtkWidget *table; + + GtkWidget *frame = gtk_frame_new(title); + gtk_frame_set_label_align(GTK_FRAME(frame), 0.0, 0.5); + gtk_label_set_use_markup( + GTK_LABEL(gtk_frame_get_label_widget(GTK_FRAME(frame))), + TRUE + ); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); + + GtkWidget *alignment = gtk_alignment_new(0.5, 0.5, 1.0, 1.0); + gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 12, 0); + gtk_container_add(GTK_CONTAINER(frame), alignment); + + table = gtk_table_new(num_row, num_col, FALSE); + gtk_table_set_row_spacings(GTK_TABLE(table), 6); + gtk_table_set_col_spacings(GTK_TABLE(table), 6); + gtk_container_set_border_width(GTK_CONTAINER(table), 6); + gtk_container_add(GTK_CONTAINER(alignment), table); + + if(out_frame) + *out_frame = frame; + + return table; +} + + +static GtkWidget* +add_row(GtkTable * table, const char * label, const char * value, int row) +{ + GtkWidget *header = gtk_label_new(label); + gtk_misc_set_alignment(GTK_MISC(header), 0.0, 0.5); + gtk_table_attach( + table, header, + 0, 1, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0 + ); + + GtkWidget *label_widget = gtk_label_new(value); + gtk_misc_set_alignment(GTK_MISC(label_widget), 0.0, 0.5); + gtk_table_attach( + table, label_widget, + 1, 2, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0 + ); + return label_widget; +} + + +static GtkWidget * +procman_create_sysinfo_view(void) +{ + GtkWidget *hbox; + GtkWidget *vbox; + + SysInfo *data = get_sysinfo();; + + GtkWidget * logo; + + GtkWidget *distro_frame; + GtkWidget *distro_release_label; + GtkWidget *distro_table; + + GtkWidget *hardware_table; + GtkWidget *memory_label; + GtkWidget *processor_label; + + GtkWidget *disk_space_table; + GtkWidget *disk_space_label; + + GtkWidget *header; + + gchar *markup; + + + hbox = gtk_hbox_new(FALSE, 12); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 6); + + /* left-side logo */ + + logo = gtk_image_new_from_file(DATADIR "/pixmaps/mate-system-monitor/side.png"); + gtk_misc_set_alignment(GTK_MISC(logo), 0.5, 0.0); + gtk_misc_set_padding(GTK_MISC(logo), 5, 12); + gtk_box_pack_start(GTK_BOX(hbox), logo, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(logo), "expose-event", + G_CALLBACK(sysinfo_logo_expose), NULL); + + vbox = gtk_vbox_new(FALSE, 12); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 12); + gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0); + + // hostname + + markup = g_strdup_printf("<big><big><b><u>%s</u></b></big></big>", + data->hostname.c_str()); + GtkWidget *hostname_frame = gtk_frame_new(markup); + g_free(markup); + gtk_frame_set_label_align(GTK_FRAME(hostname_frame), 0.0, 0.5); + gtk_label_set_use_markup( + GTK_LABEL(gtk_frame_get_label_widget(GTK_FRAME(hostname_frame))), + TRUE + ); + gtk_frame_set_shadow_type(GTK_FRAME(hostname_frame), GTK_SHADOW_NONE); + gtk_box_pack_start(GTK_BOX(vbox), hostname_frame, FALSE, FALSE, 0); + + + /* distro section */ + + unsigned table_size = 2; + if (data->mate_version != "") + table_size++; + distro_table = add_section(GTK_BOX(vbox), "???", table_size, 1, &distro_frame); + + unsigned table_count = 0; + + distro_release_label = gtk_label_new("???"); + gtk_misc_set_alignment(GTK_MISC(distro_release_label), 0.0, 0.5); + gtk_table_attach( + GTK_TABLE(distro_table), distro_release_label, + 0, 1, table_count, table_count+1, + GTK_FILL, GTK_FILL, 0, 0 + ); + table_count++; + data->set_distro_labels(gtk_frame_get_label_widget(GTK_FRAME(distro_frame)), distro_release_label); + + markup = g_strdup_printf(_("Kernel %s"), data->kernel.c_str()); + header = gtk_label_new(markup); + g_free(markup); + gtk_misc_set_alignment(GTK_MISC(header), 0.0, 0.5); + gtk_table_attach( + GTK_TABLE(distro_table), header, + 0, 1, table_count, table_count + 1, + GTK_FILL, GTK_FILL, 0, 0 + ); + table_count++; + + if (data->mate_version != "") + { + markup = g_strdup_printf(_("MATE %s"), data->mate_version.c_str()); + header = gtk_label_new(markup); + g_free(markup); + gtk_misc_set_alignment(GTK_MISC(header), 0.0, 0.5); + gtk_table_attach( + GTK_TABLE(distro_table), header, + 0, 1, table_count, table_count + 1, + GTK_FILL, GTK_FILL, 0, 0 + ); + table_count++; + } + + /* hardware section */ + + markup = g_strdup_printf(_("<b>Hardware</b>")); + hardware_table = add_section(GTK_BOX(vbox), markup, data->processors.size(), 2, NULL); + g_free(markup); + + markup = procman::format_size(data->memory_bytes); + memory_label = add_row(GTK_TABLE(hardware_table), _("Memory:"), + markup, 0); + g_free(markup); + + for (guint i = 0; i < data->processors.size(); ++i) { + const gchar * t; + if (data->processors.size() > 1) { + markup = g_strdup_printf(_("Processor %d:"), i); + t = markup; + } + else { + markup = NULL; + t = _("Processor:"); + } + + processor_label = add_row(GTK_TABLE(hardware_table), t, + data->processors[i].c_str(), 1 + i); + + if(markup) + g_free(markup); + } + + /* disk space section */ + + markup = g_strdup_printf(_("<b>System Status</b>")); + disk_space_table = add_section(GTK_BOX(vbox), markup, 1, 2, NULL); + g_free(markup); + + markup = procman::format_size(data->free_space_bytes); + disk_space_label = add_row(GTK_TABLE(disk_space_table), + _("Available disk space:"), markup, + 0); + g_free(markup); + + return hbox; +} + + + +namespace procman +{ + void build_sysinfo_ui() + { + static GtkWidget* ui; + + if (!ui) { + ProcData* procdata = ProcData::get_instance(); + ui = procman_create_sysinfo_view(); + GtkBox* box = GTK_BOX(gtk_notebook_get_nth_page(GTK_NOTEBOOK(procdata->notebook), + PROCMAN_TAB_SYSINFO)); + gtk_box_pack_start(box, ui, TRUE, TRUE, 0); + gtk_widget_show_all(ui); + } + } +} diff --git a/src/sysinfo.h b/src/sysinfo.h new file mode 100644 index 0000000..9ed9c1d --- /dev/null +++ b/src/sysinfo.h @@ -0,0 +1,9 @@ +#ifndef H_MATE_SYSTEM_MONITOR_SYSINFO_H_1155594649 +#define H_MATE_SYSTEM_MONITOR_SYSINFO_H_1155594649 + +namespace procman +{ + void build_sysinfo_ui(); +} + +#endif /* H_MATE_SYSTEM_MONITOR_SYSINFO_H_1155594649 */ diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..907bf80 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,510 @@ +#include <config.h> + +#include <glib/gi18n.h> +#include <glib/gstring.h> +#include <gtk/gtk.h> + +#include <glibtop/proctime.h> +#include <glibtop/procstate.h> +#include <unistd.h> + +#include <stddef.h> +#include <cstring> + +#include "util.h" +#include "procman.h" + +extern "C" { +#include "e_date.h" +} + + +static 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 + */ +static char * +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); +} + + + +GtkWidget* +procman_make_label_for_mmaps_or_ofiles(const char *format, + const char *process_name, + unsigned pid) +{ + GtkWidget *label; + char *name, *title; + + name = mnemonic_safe_process_name (process_name); + title = g_strdup_printf(format, name, pid); + label = gtk_label_new_with_mnemonic (title); + gtk_misc_set_alignment (GTK_MISC (label), 0.0f, 0.5f); + + 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, guint64 max_size, bool want_bits) +{ + enum { + K_INDEX, + M_INDEX, + G_INDEX + }; + + struct Format { + guint64 factor; + const char* string; + }; + + const Format all_formats[2][3] = { + { { 1UL << 10, N_("%.1f KiB") }, + { 1UL << 20, N_("%.1f MiB") }, + { 1UL << 30, N_("%.1f GiB") } }, + { { 1000, N_("%.1f kbit") }, + { 1000000, N_("%.1f Mbit") }, + { 1000000000, N_("%.1f Gbit") } } + }; + + const Format (&formats)[3] = all_formats[want_bits ? 1 : 0]; + + if (want_bits) { + size *= 8; + max_size *= 8; + } + + if (max_size == 0) + max_size = size; + + if (max_size < formats[K_INDEX].factor) { + const char *format = (want_bits + ? dngettext(GETTEXT_PACKAGE, "%u bit", "%u bits", (guint) size) + : dngettext(GETTEXT_PACKAGE, "%u byte", "%u bytes",(guint) size)); + return g_strdup_printf (format, (guint) size); + } else { + guint64 factor; + const char* format = NULL; + + if (max_size < formats[M_INDEX].factor) { + factor = formats[K_INDEX].factor; + format = formats[K_INDEX].string; + } else if (max_size < formats[G_INDEX].factor) { + factor = formats[M_INDEX].factor; + format = formats[M_INDEX].string; + } else { + factor = formats[G_INDEX].factor; + format = formats[G_INDEX].string; + } + + return g_strdup_printf(_(format), size / (double)factor); + } +} + + + +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("MATE_SYSTEM_MONITOR_DEBUG") != NULL; + init = TRUE; + } + + return enabled; +} + + +static double get_relative_time(void) +{ + static unsigned long start_time; + GTimeVal tv; + + if (G_UNLIKELY(!start_time)) { + glibtop_proc_time buf; + glibtop_get_proc_time(&buf, getpid()); + start_time = buf.start_time; + } + + g_get_current_time(&tv); + return (tv.tv_sec - start_time) + 1e-6 * tv.tv_usec; +} + + +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_debug("[%.3f %s:%d %s] %s", 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 = procman::format_size(size); + g_object_set(renderer, "text", str, NULL); + g_free(str); + } + + + /* + Same as above but handles size == 0 as not available + */ + void size_na_cell_data_func(GtkTreeViewColumn *, GtkCellRenderer *renderer, + GtkTreeModel *model, GtkTreeIter *iter, + gpointer user_data) + { + const guint index = GPOINTER_TO_UINT(user_data); + + guint64 size; + GValue value = { 0 }; + + gtk_tree_model_get_value(model, iter, index, &value); + + switch (G_VALUE_TYPE(&value)) { + case G_TYPE_ULONG: + size = g_value_get_ulong(&value); + break; + + case G_TYPE_UINT64: + size = g_value_get_uint64(&value); + break; + + default: + g_assert_not_reached(); + } + + g_value_unset(&value); + + if (size == 0) + g_object_set(renderer, "markup", _("<i>N/A</i>"), NULL); + else { + char *str = procman::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 / ProcData::get_instance()->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); + } + + + 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 (!current_value or std::strcmp(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, guint64 max_rate, bool want_bits) + { + char* bytes = procman::format_size(rate, max_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, guint64 max_rate) + { + return procman::format_size(rate, max_rate, ProcData::get_instance()->config.network_in_bits); + } + + + std::string format_network_rate(guint64 rate, guint64 max_rate) + { + return procman::format_rate(rate, max_rate, ProcData::get_instance()->config.network_in_bits); + } + +} + + + diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..1a15b4b --- /dev/null +++ b/src/util.h @@ -0,0 +1,151 @@ +// -*- mode: c++ -*- + +#ifndef H_MATE_SYSTEM_MONITOR_UTIL_1123178725 +#define H_MATE_SYSTEM_MONITOR_UTIL_1123178725 + +#include <glib.h> +#include <gtk/gtk.h> +#include <stddef.h> +#include <cstring> +#include <string> +#include <functional> +#include <algorithm> + +using std::string; + +template<typename T> +inline int procman_cmp(T x, T y) +{ + if (x == y) + return 0; + + if (x < y) + return -1; + + return 1; +} + +#define PROCMAN_CMP(X, Y) procman_cmp((X), (Y)) +#define PROCMAN_RCMP(X, Y) procman_cmp((Y), (X)); + +GtkWidget* +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; + + +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__) + +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; +} + + + + +template<typename Map> +class UnrefMapValues + : public std::unary_function<void, Map> +{ +public: + void operator()(const typename Map::value_type &it) const + { + if (it.second) + g_object_unref(it.second); + } +}; + + +template<typename Map> +inline void unref_map_values(Map &map) +{ + std::for_each(map.begin(), map.end(), UnrefMapValues<Map>()); +} + + +namespace procman +{ + void size_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 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); + + 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, guint64 max = 0, bool want_bits = false); + + std::string format_rate(guint64 rate, guint64 max_rate = 0, bool want_bits = false); + + std::string format_network(guint64 rate, guint64 max_rate = 0); + std::string format_network_rate(guint64 rate, guint64 max_rate = 0); +} + + +#endif /* H_MATE_SYSTEM_MONITOR_UTIL_1123178725 */ |