/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include <config.h>
#include <gtk/gtk.h>
#include <glibtop/mountlist.h>
#include <glibtop/fsusage.h>
#include <glib/gi18n.h>
#include "disks.h"
#include "application.h"
#include "util.h"
#include "settings-keys.h"
#include "legacy/treeview.h"
enum DiskColumns
{
/* string columns* */
DISK_DEVICE,
DISK_DIR,
DISK_TYPE,
DISK_TOTAL,
DISK_FREE,
DISK_AVAIL,
/* USED has to be the last column */
DISK_USED,
// then unvisible columns
/* PixBuf column */
DISK_ICON,
/* numeric columns */
DISK_USED_PERCENTAGE,
DISK_N_COLUMNS
};
static void
cb_sort_changed (GtkTreeSortable *model, gpointer data)
{
GsmApplication *app = (GsmApplication *) data;
gsm_tree_view_save_state (GSM_TREE_VIEW (app->disk_list));
}
static void
fsusage_stats(const glibtop_fsusage *buf,
guint64 *bused, guint64 *bfree, guint64 *bavail, guint64 *btotal,
gint *percentage)
{
guint64 total = buf->blocks * buf->block_size;
if (!total) {
/* not a real device */
*btotal = *bfree = *bavail = *bused = 0ULL;
*percentage = 0;
} else {
int percent;
*btotal = total;
*bfree = buf->bfree * buf->block_size;
*bavail = buf->bavail * buf->block_size;
*bused = *btotal - *bfree;
/* percent = 100.0f * *bused / *btotal; */
percent = 100 * *bused / (*bused + *bavail);
*percentage = CLAMP(percent, 0, 100);
}
}
static const char* get_icon_for_path(const char* path)
{
GVolumeMonitor *monitor;
GList *mounts;
uint i;
GMount *mount;
GIcon *icon;
const char* name = "";
monitor = g_volume_monitor_get ();
mounts = g_volume_monitor_get_mounts (monitor);
for (i = 0; i < g_list_length (mounts); i++) {
mount = G_MOUNT (g_list_nth_data(mounts, i));
if (strcmp(g_mount_get_name(mount), path))
continue;
icon = g_mount_get_icon (mount);
if (!icon)
continue;
name = g_icon_to_string (icon);
g_object_unref (icon);
}
g_list_free_full (mounts, g_object_unref);
return name;
}
static GdkPixbuf*
get_icon_for_device(const char *mountpoint)
{
const char* icon_name = get_icon_for_path(mountpoint);
if (!strcmp(icon_name, ""))
// FIXME: defaults to a safe value
icon_name = "drive-harddisk"; // get_icon_for_path("/");
return gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), icon_name, 24, GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
}
static gboolean
find_disk_in_model(GtkTreeModel *model, const char *mountpoint,
GtkTreeIter *result)
{
GtkTreeIter iter;
gboolean found = FALSE;
if (gtk_tree_model_get_iter_first(model, &iter)) {
do {
char *dir;
gtk_tree_model_get(model, &iter,
DISK_DIR, &dir,
-1);
if (dir && !strcmp(dir, mountpoint)) {
*result = iter;
found = TRUE;
}
g_free(dir);
} while (!found && gtk_tree_model_iter_next(model, &iter));
}
return found;
}
static void
remove_old_disks(GtkTreeModel *model, const glibtop_mountentry *entries, guint n)
{
GtkTreeIter iter;
if (!gtk_tree_model_get_iter_first(model, &iter))
return;
while (true) {
char *dir;
guint i;
gboolean found = FALSE;
gtk_tree_model_get(model, &iter,
DISK_DIR, &dir,
-1);
for (i = 0; i != n; ++i) {
if (!strcmp(dir, entries[i].mountdir)) {
found = TRUE;
break;
}
}
g_free(dir);
if (!found) {
if (!gtk_list_store_remove(GTK_LIST_STORE(model), &iter))
break;
else
continue;
}
if (!gtk_tree_model_iter_next(model, &iter))
break;
}
}
static void
add_disk(GtkListStore *list, const glibtop_mountentry *entry, bool show_all_fs)
{
GdkPixbuf* pixbuf;
GtkTreeIter iter;
glibtop_fsusage usage;
guint64 bused, bfree, bavail, btotal;
gint percentage;
glibtop_get_fsusage(&usage, entry->mountdir);
if (not show_all_fs and usage.blocks == 0) {
if (find_disk_in_model(GTK_TREE_MODEL(list), entry->mountdir, &iter))
gtk_list_store_remove(list, &iter);
return;
}
fsusage_stats(&usage, &bused, &bfree, &bavail, &btotal, &percentage);
pixbuf = get_icon_for_device(entry->mountdir);
/* if we can find a row with the same mountpoint, we get it but we
still need to update all the fields.
This makes selection persistent.
*/
if (!find_disk_in_model(GTK_TREE_MODEL(list), entry->mountdir, &iter))
gtk_list_store_append(list, &iter);
gtk_list_store_set(list, &iter,
DISK_ICON, pixbuf,
DISK_DEVICE, entry->devname,
DISK_DIR, entry->mountdir,
DISK_TYPE, entry->type,
DISK_USED_PERCENTAGE, percentage,
DISK_TOTAL, btotal,
DISK_FREE, bfree,
DISK_AVAIL, bavail,
DISK_USED, bused,
-1);
}
static void
mount_changed (GVolumeMonitor *monitor, GMount *mount, GsmApplication *app)
{
disks_update(app);
}
static gboolean
cb_timeout (gpointer data)
{
GsmApplication *app = (GsmApplication *) data;
disks_update (app);
return G_SOURCE_CONTINUE;
}
void
disks_update(GsmApplication *app)
{
GtkListStore *list;
glibtop_mountentry * entries;
glibtop_mountlist mountlist;
guint i;
gboolean show_all_fs;
list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(app->disk_list)));
show_all_fs = app->settings->get_boolean (GSM_SETTING_SHOW_ALL_FS);
entries = glibtop_get_mountlist (&mountlist, show_all_fs);
remove_old_disks(GTK_TREE_MODEL(list), entries, mountlist.number);
for (i = 0; i < mountlist.number; i++)
add_disk(list, &entries[i], show_all_fs);
g_free(entries);
}
static void
init_volume_monitor (GsmApplication *app)
{
GVolumeMonitor *monitor = g_volume_monitor_get ();
g_signal_connect (monitor, "mount-added", G_CALLBACK (mount_changed), app);
g_signal_connect (monitor, "mount-changed", G_CALLBACK (mount_changed), app);
g_signal_connect (monitor, "mount-removed", G_CALLBACK (mount_changed), app);
}
void
disks_freeze (GsmApplication *app)
{
if (app->disk_timeout) {
g_source_remove (app->disk_timeout);
app->disk_timeout = 0;
}
}
void
disks_thaw (GsmApplication *app)
{
if (app->disk_timeout)
return;
app->disk_timeout = g_timeout_add (app->config.disks_update_interval,
cb_timeout,
app);
}
void
disks_reset_timeout (GsmApplication *app)
{
disks_freeze (app);
disks_thaw (app);
}
static void
cb_disk_columns_changed(GtkTreeView *treeview, gpointer data)
{
gsm_tree_view_save_state (GSM_TREE_VIEW (treeview));
}
static void open_dir(GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer user_data)
{
GtkTreeIter iter;
GtkTreeModel *model;
char *dir, *url;
model = gtk_tree_view_get_model(tree_view);
if (!gtk_tree_model_get_iter(model, &iter, path)) {
char *p;
p = gtk_tree_path_to_string(path);
g_warning("Cannot get iter for path '%s'\n", p);
g_free(p);
return;
}
gtk_tree_model_get(model, &iter, DISK_DIR, &dir, -1);
url = g_strdup_printf("file://%s", dir);
GError* error = 0;
if (!g_app_info_launch_default_for_uri(url, NULL, &error)) {
g_warning("Cannot open '%s' : %s\n", url, error->message);
g_error_free(error);
}
g_free(url);
g_free(dir);
}
static void
cb_disk_list_destroying (GtkWidget *self, gpointer data)
{
g_signal_handlers_disconnect_by_func(self, (gpointer) cb_disk_columns_changed, data);
g_signal_handlers_disconnect_by_func (gtk_tree_view_get_model (GTK_TREE_VIEW(self)),
(gpointer) cb_sort_changed,
data);
}
void
create_disk_view(GsmApplication *app, GtkBuilder *builder)
{
GtkScrolledWindow *scrolled;
GsmTreeView *disk_tree;
GtkListStore *model;
GtkTreeViewColumn *col;
GtkCellRenderer *cell;
guint i;
init_volume_monitor (app);
const gchar * const titles[] = {
N_("Device"),
N_("Directory"),
N_("Type"),
N_("Total"),
N_("Free"),
N_("Available"),
N_("Used")
};
scrolled = GTK_SCROLLED_WINDOW (gtk_builder_get_object (builder, "disks_scrolled"));
model = gtk_list_store_new(DISK_N_COLUMNS, /* n columns */
G_TYPE_STRING, /* DISK_DEVICE */
G_TYPE_STRING, /* DISK_DIR */
G_TYPE_STRING, /* DISK_TYPE */
G_TYPE_UINT64, /* DISK_TOTAL */
G_TYPE_UINT64, /* DISK_FREE */
G_TYPE_UINT64, /* DISK_AVAIL */
G_TYPE_UINT64, /* DISK_USED */
GDK_TYPE_PIXBUF, /* DISK_ICON */
G_TYPE_INT /* DISK_USED_PERCENTAGE */
);
disk_tree = gsm_tree_view_new (g_settings_get_child (app->settings->gobj(), GSM_SETTINGS_CHILD_DISKS), TRUE);
gtk_tree_view_set_model (GTK_TREE_VIEW (disk_tree), GTK_TREE_MODEL (model));
g_signal_connect(G_OBJECT(disk_tree), "row-activated", G_CALLBACK(open_dir), NULL);
app->disk_list = disk_tree;
gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET (disk_tree));
g_object_unref(G_OBJECT(model));
/* icon + device */
col = gtk_tree_view_column_new();
cell = gtk_cell_renderer_pixbuf_new();
gtk_tree_view_column_pack_start(col, cell, FALSE);
gtk_tree_view_column_set_min_width(col, 30);
gtk_tree_view_column_set_attributes(col, cell, "pixbuf", DISK_ICON,
NULL);
cell = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start(col, cell, FALSE);
gtk_tree_view_column_set_attributes(col, cell, "text", DISK_DEVICE,
NULL);
gtk_tree_view_column_set_title(col, _(titles[DISK_DEVICE]));
gtk_tree_view_column_set_sort_column_id(col, DISK_DEVICE);
gtk_tree_view_column_set_reorderable(col, TRUE);
gtk_tree_view_column_set_resizable(col, TRUE);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);
/* sizes - used */
for (i = DISK_DIR; i <= DISK_AVAIL; i++) {
cell = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new();
gtk_tree_view_column_pack_start(col, cell, TRUE);
gtk_tree_view_column_set_title(col, _(titles[i]));
gtk_tree_view_column_set_resizable(col, TRUE);
gtk_tree_view_column_set_sort_column_id(col, i);
gtk_tree_view_column_set_reorderable(col, TRUE);
gtk_tree_view_column_set_min_width(col, i == DISK_TYPE ? 40 : 72);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);
switch (i) {
case DISK_TOTAL:
case DISK_FREE:
case DISK_AVAIL:
g_object_set(cell, "xalign", 1.0f, NULL);
gtk_tree_view_column_set_cell_data_func(col, cell,
&procman::size_si_cell_data_func,
GUINT_TO_POINTER(i),
NULL);
break;
default:
gtk_tree_view_column_set_attributes(col, cell,
"text", i,
NULL);
break;
}
}
/* used + percentage */
col = gtk_tree_view_column_new();
cell = gtk_cell_renderer_text_new();
g_object_set(cell, "xalign", 1.0f, NULL);
gtk_tree_view_column_set_min_width(col, 72);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_pack_start(col, cell, FALSE);
gtk_tree_view_column_set_cell_data_func(col, cell,
&procman::size_si_cell_data_func,
GUINT_TO_POINTER(DISK_USED),
NULL);
gtk_tree_view_column_set_title(col, _(titles[DISK_USED]));
cell = gtk_cell_renderer_progress_new();
gtk_cell_renderer_set_padding(cell, 4.0f, 4.0f);
gtk_tree_view_column_pack_start(col, cell, TRUE);
gtk_tree_view_column_set_attributes(col, cell, "value",
DISK_USED_PERCENTAGE, NULL);
gtk_tree_view_column_set_resizable(col, TRUE);
gtk_tree_view_column_set_sort_column_id(col, DISK_USED);
gtk_tree_view_column_set_reorderable(col, TRUE);
gsm_tree_view_append_and_bind_column (GSM_TREE_VIEW (disk_tree), col);
/* numeric sort */
gsm_tree_view_load_state (GSM_TREE_VIEW (disk_tree));
g_signal_connect (G_OBJECT(disk_tree), "destroy",
G_CALLBACK(cb_disk_list_destroying),
app);
g_signal_connect (G_OBJECT(disk_tree), "columns-changed",
G_CALLBACK(cb_disk_columns_changed), app);
g_signal_connect (G_OBJECT (model), "sort-column-changed",
G_CALLBACK (cb_sort_changed), app);
app->settings->signal_changed(GSM_SETTING_SHOW_ALL_FS).connect ([app](const Glib::ustring&) { disks_update (app); disks_reset_timeout (app); });
gtk_widget_show (GTK_WIDGET (disk_tree));
}