/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include <config.h>
#include <gtkmm/messagedialog.h>
#include <glib/gi18n.h>
#include <glibtop/procopenfiles.h>
#include <sys/wait.h>
#include <set>
#include <string>
#include <sstream>
#include <iterator>
#include <glibmm/regex.h>
#include "application.h"
#include "lsof.h"
#include "util.h"
using std::string;
namespace
{
class Lsof
{
Glib::RefPtr<Glib::Regex> re;
bool matches(const string &filename) const
{
return this->re->match(filename);
}
public:
Lsof(const string &pattern, bool caseless)
{
Glib::RegexCompileFlags flags = static_cast<Glib::RegexCompileFlags>(0);
if (caseless)
flags |= Glib::REGEX_CASELESS;
this->re = Glib::Regex::create(pattern, flags);
}
template<typename OutputIterator>
void search(const ProcInfo &info, OutputIterator out) const
{
glibtop_open_files_entry *entries;
glibtop_proc_open_files buf;
entries = glibtop_get_proc_open_files(&buf, info.pid);
for (unsigned i = 0; i != buf.number; ++i) {
if (entries[i].type & GLIBTOP_FILE_TYPE_FILE) {
const string filename(entries[i].info.file.name);
if (this->matches(filename))
*out++ = filename;
}
}
g_free(entries);
}
};
// GUI Stuff
enum ProcmanLsof {
PROCMAN_LSOF_COL_PIXBUF,
PROCMAN_LSOF_COL_PROCESS,
PROCMAN_LSOF_COL_PID,
PROCMAN_LSOF_COL_FILENAME,
PROCMAN_LSOF_NCOLS
};
struct GUI : private procman::NonCopyable {
GtkListStore *model;
GtkSearchEntry *entry;
GtkWindow *window;
GtkLabel *count;
GsmApplication *app;
bool case_insensitive;
bool regex_error_displayed;
GUI()
: model(NULL),
entry(NULL),
window(NULL),
count(NULL),
app(NULL),
case_insensitive(),
regex_error_displayed(false)
{
procman_debug("New Lsof GUI %p", this);
}
~GUI()
{
procman_debug("Destroying Lsof GUI %p", this);
}
void update_count(unsigned count)
{
gchar *title;
if (this->pattern().length() == 0) {
title = g_strdup_printf (ngettext("%d open file", "%d open files", count), count);
} else {
title = g_strdup_printf (ngettext("%d matching open file", "%d matching open files", count), count);
}
gtk_window_set_title(this->window, title);
g_free (title);
}
string pattern() const
{
return gtk_entry_get_text(GTK_ENTRY (this->entry));
}
void search()
{
typedef std::set<string> MatchSet;
bool regex_error = false;
gtk_list_store_clear(this->model);
try {
Lsof lsof(this->pattern(), this->case_insensitive);
unsigned count = 0;
for (const auto& v : app->processes) {
const auto& info = v.second;
MatchSet matches;
lsof.search(info, std::inserter(matches, matches.begin()));
count += matches.size();
for (const auto& match : matches) {
GtkTreeIter file;
gtk_list_store_append(this->model, &file);
gtk_list_store_set(this->model, &file,
PROCMAN_LSOF_COL_PIXBUF, info.pixbuf->gobj(),
PROCMAN_LSOF_COL_PROCESS, info.name.c_str(),
PROCMAN_LSOF_COL_PID, info.pid,
PROCMAN_LSOF_COL_FILENAME, match.c_str(),
-1);
}
}
this->update_count(count);
}
catch (Glib::RegexError& error) {
regex_error = true;
}
if (regex_error && !this->regex_error_displayed) {
this->regex_error_displayed = true;
gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(entry)), GTK_STYLE_CLASS_ERROR);
}
else if (!regex_error && this->regex_error_displayed) {
this->regex_error_displayed = false;
gtk_style_context_remove_class(gtk_widget_get_style_context(GTK_WIDGET(entry)), GTK_STYLE_CLASS_ERROR);
}
}
static void search_changed(GtkSearchEntry *, gpointer data)
{
static_cast<GUI*>(data)->search();
}
static void close_button_clicked(GtkButton *, gpointer data)
{
GUI *gui = static_cast<GUI*>(data);
gtk_widget_destroy(GTK_WIDGET(gui->window));
delete gui;
}
static void case_button_toggled(GtkToggleButton *button, gpointer data)
{
bool state = gtk_toggle_button_get_active(button);
static_cast<GUI*>(data)->case_insensitive = state;
}
static gboolean window_delete_event(GtkWidget *, GdkEvent *, gpointer data)
{
delete static_cast<GUI*>(data);
return FALSE;
}
};
}
void procman_lsof(GsmApplication *app)
{
GtkListStore *model = \
gtk_list_store_new(PROCMAN_LSOF_NCOLS,
GDK_TYPE_PIXBUF, // PROCMAN_LSOF_COL_PIXBUF
G_TYPE_STRING, // PROCMAN_LSOF_COL_PROCESS
G_TYPE_UINT, // PROCMAN_LSOF_COL_PID
G_TYPE_STRING // PROCMAN_LSOF_COL_FILENAME
);
GtkTreeView *tree = GTK_TREE_VIEW (gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)));
g_object_unref(model);
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
// PIXBUF / PROCESS
column = gtk_tree_view_column_new();
renderer = gtk_cell_renderer_pixbuf_new();
gtk_tree_view_column_pack_start(column, renderer, FALSE);
gtk_tree_view_column_set_attributes(column, renderer,
"pixbuf", PROCMAN_LSOF_COL_PIXBUF,
NULL);
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start(column, renderer, FALSE);
gtk_tree_view_column_set_attributes(column, renderer,
"text", PROCMAN_LSOF_COL_PROCESS,
NULL);
gtk_tree_view_column_set_title(column, _("Process"));
gtk_tree_view_column_set_sort_column_id(column, PROCMAN_LSOF_COL_PROCESS);
gtk_tree_view_column_set_resizable(column, TRUE);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
gtk_tree_view_column_set_min_width(column, 10);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), PROCMAN_LSOF_COL_PROCESS,
GTK_SORT_ASCENDING);
// PID
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(_("PID"), renderer,
"text", PROCMAN_LSOF_COL_PID,
NULL);
gtk_tree_view_column_set_sort_column_id(column, PROCMAN_LSOF_COL_PID);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
// FILENAME
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(_("Filename"), renderer,
"text", PROCMAN_LSOF_COL_FILENAME,
NULL);
gtk_tree_view_column_set_sort_column_id(column, PROCMAN_LSOF_COL_FILENAME);
gtk_tree_view_column_set_resizable(column, TRUE);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
GtkWindow *dialog;
GtkBuilder *builder = gtk_builder_new ();
gtk_builder_add_from_resource (builder, "/org/gnome/gnome-system-monitor/data/lsof.ui", NULL);
dialog = GTK_WINDOW (gtk_builder_get_object (builder, "lsof_dialog"));
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (app->main_window));
GtkSearchEntry *entry = GTK_SEARCH_ENTRY (gtk_builder_get_object (builder, "entry"));
GtkCheckButton *case_button = GTK_CHECK_BUTTON (gtk_builder_get_object (builder, "case_button"));
// Scrolled TreeView
GtkScrolledWindow *scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "scrolled"));
gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET (tree));
GUI *gui = new GUI; // wil be deleted by the close button or delete-event
gui->app = app;
gui->model = model;
gui->window = dialog;
gui->entry = entry;
gui->case_insensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (case_button));
g_signal_connect(G_OBJECT(entry), "search-changed",
G_CALLBACK(GUI::search_changed), gui);
g_signal_connect(G_OBJECT(case_button), "toggled",
G_CALLBACK(GUI::case_button_toggled), gui);
g_signal_connect(G_OBJECT(dialog), "delete-event",
G_CALLBACK(GUI::window_delete_event), gui);
gtk_builder_connect_signals (builder, NULL);
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (GsmApplication::get()->main_window));
gtk_widget_show_all(GTK_WIDGET (dialog));
gui->search ();
g_object_unref (G_OBJECT (builder));
}