/*
 *    Hardinfo2 - System information and benchmark
 *    Copyright (C) 2003-2007 L. A. F. Pereira <l@tia.mat.br>
 *    Copyright (C) 2024-2026 hardinfo2 project
 *
 *    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, version 2 or later.
 *
 *    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */
#define _GNU_SOURCE

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

#include <gtk/gtk.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <stdbool.h>

#include "config.h"

#include "hardinfo.h"

#include "shell.h"
#include "syncmanager.h"
#include "iconcache.h"
#include "menu.h"
#include "stock.h"
#include "uri_handler.h"

#include "callbacks.h"

gboolean updating=TRUE;
gboolean closing=FALSE;

struct UpdateTableItem {
    union {
        GtkWidget *widget;
        GtkTreeIter *iter;
    };
    gboolean is_iter;
};

/*
 * Internal Prototypes ********************************************************
 */

static void create_window(void);
static ShellTree *tree_new(void);
static ShellInfoTree *info_tree_new(void);

static void module_selected(gpointer data);
static void module_selected_show_info(ShellModuleEntry * entry,
				      gboolean reload);
static void info_selected(GtkTreeSelection * ts, gpointer data);
static void info_selected_show_extra(const gchar *tag);
static gboolean reload_section(gpointer data);
static gboolean rescan_section(gpointer data);
static gboolean update_field(gpointer data);
static gboolean is_search_supported_for_current_view(void);
static void update_search_widgets_state(void);
#if GTK_CHECK_VERSION(3,0,0)
static GSettings *settings=NULL;
#endif
/*
 * Globals ********************************************************************
 */

static Shell *shell = NULL;
static GHashTable *update_tbl = NULL;
static GSList *update_sfusrc = NULL;

gchar *lginterval = NULL;

/*
 * Code :) ********************************************************************
 */

static gint natural_strcmp(const gchar *a, const gchar *b) {
    const gchar *pa, *pb;
    gint len_a, len_b;

    if (!a && !b)
        return 0;
    if (!a)
        return -1;
    if (!b)
        return 1;

    while (*a && *b) {
        if ((g_ascii_isdigit(*a) || ((*a == '-' || *a == '+') && g_ascii_isdigit(*(a+1)))) &&
            (g_ascii_isdigit(*b) || ((*b == '-' || *b == '+') && g_ascii_isdigit(*(b+1))))) {

            gboolean neg_a = (*a == '-');
            gboolean neg_b = (*b == '-');

            if (*a == '-' || *a == '+')
                a++;
            if (*b == '-' || *b == '+')
                b++;

            while (*a == '0')
                a++;
            while (*b == '0')
                b++;

            pa = a;
            pb = b;

            while (g_ascii_isdigit(*a))
                a++;
            while (g_ascii_isdigit(*b))
                b++;

            len_a = a - pa;
            len_b = b - pb;

            if (neg_a == neg_b) {
                if (len_a != len_b)
                    return neg_a ? (len_b - len_a) : (len_a - len_b);
                while (pa < a) {
                    if (*pa != *pb)
                        return neg_a ? (*pb - *pa) : (*pa - *pb);
                    pa++; pb++;
                }
            } else {
                return neg_a ? -1 : 1;
            }
        } else {
            if (*a != *b)
                return (guchar)*a - (guchar)*b;
            a++; b++;
        }
    }
    return *a ? 1 : (*b ? -1 : 0);
}

static gint info_tree_natural_compare(GtkTreeModel *model, GtkTreeIter *a,
                                      GtkTreeIter *b, gpointer user_data)
{
    gint col = GPOINTER_TO_INT(user_data);
    gchar *str_a = NULL, *str_b = NULL;
    gtk_tree_model_get(model, a, col, &str_a, -1);
    gtk_tree_model_get(model, b, col, &str_b, -1);
    gint ret = natural_strcmp(str_a, str_b);
    g_free(str_a);
    g_free(str_b);
    return ret;
}

static gint info_tree_default_strcmp(GtkTreeModel *model, GtkTreeIter *a,
                                    GtkTreeIter *b, gpointer user_data)
{
    gint col = GPOINTER_TO_INT(user_data);
    gchar *str_a = NULL, *str_b = NULL;
    gtk_tree_model_get(model, a, col, &str_a, -1);
    gtk_tree_model_get(model, b, col, &str_b, -1);
    gint ret = g_strcmp0(str_a, str_b);
    g_free(str_a);
    g_free(str_b);
    return ret;
}

static time_t parse_boot_date(const char *str) {
    /* format: "Www Mmm dd hh:mm:ss yyyy" or "Www Mmm d hh:mm:ss yyyy" */
    struct tm tm = {0};
    char mon[4] = {0};
    int day, year, hour, min, sec, i;
    static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };

    if (!str)
        return 0;

    if (sscanf(str, "%*3s %3s %d %d:%d:%d %d", mon, &day, &hour, &min, &sec, &year) >= 6) {
        i=0;
        while(months[i]) {
            if (g_ascii_strcasecmp(mon, months[i]) == 0) {
              tm.tm_mon = i;
              break;
            }
	    i++;
        }

        tm.tm_mday = day;
        tm.tm_year = year - 1900;
        tm.tm_hour = hour;
        tm.tm_min = min;
        tm.tm_sec = sec;

        return mktime(&tm);
    }

    return 0;
}

static GtkWidget *search_entry = NULL;
static GtkWidget *search_button = NULL;

// state for "find next"
static gchar *current_search_text = NULL;
static GtkTreePath *last_search_path = NULL;
static gboolean search_wrapped = FALSE;

#ifndef G_VALUE_INIT
#define G_VALUE_INIT {}
#endif

static gboolean check_node_for_search_match(GtkTreeModel *model, GtkTreeIter *iter, const gchar *search_text)
{
    int col = 0;
    while(col < INFO_TREE_NCOL) {
        GValue value = G_VALUE_INIT;
        gtk_tree_model_get_value(model, iter, col, &value);

        if (G_VALUE_TYPE(&value) == G_TYPE_STRING) {
            const gchar *cell_text = g_value_get_string(&value);

            if (cell_text && strlen(cell_text) > 0) {
                gchar *cell_lower = g_utf8_strdown(cell_text, -1);
                gchar *search_lower = g_utf8_strdown(search_text, -1);

                if (strstr(cell_lower, search_lower)) {
                    g_free(cell_lower);
                    g_free(search_lower);
                    g_value_unset(&value);
                    return TRUE;
                }
                g_free(cell_lower);
                g_free(search_lower);
            }
        }
        g_value_unset(&value);
	col++;
    }
    return FALSE;
}

static void select_and_scroll_to_found_item(GtkWidget *tree_view, GtkTreeModel *model,
                                            GtkTreeIter *iter) {
    GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));

    if (selection) {
        gtk_tree_selection_select_iter(selection, iter);

        GtkTreePath *path = gtk_tree_model_get_path(model, iter);
        if (path) {
            gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(tree_view), path, NULL, TRUE, 0.5, 0.0);

            if (last_search_path) {
                gtk_tree_path_free(last_search_path);
            }
            last_search_path = gtk_tree_path_copy(path);
            gtk_tree_path_free(path);
        }
    }
}

static gboolean search_tree_view(GtkWidget *tree_view, const gchar *search_text,
                                 gboolean find_next) {
    GtkTreeModel *model;
    GtkTreeIter iter;
    gboolean valid;
    gboolean found = FALSE;
    GtkTreePath *start_path = NULL;

    if (!search_text || strlen(search_text) == 0)
        return FALSE;

    if (!tree_view || !GTK_IS_TREE_VIEW(tree_view))
        return FALSE;

    model = gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view));
    if (!model)
        return FALSE;

    if (find_next && last_search_path) {
        start_path = gtk_tree_path_copy(last_search_path);
        gtk_tree_path_next(start_path);

        if (!gtk_tree_model_get_iter(model, &iter, start_path)) {
            gtk_tree_path_free(start_path);
            start_path = gtk_tree_path_new_first();
        }
    } else {
        start_path = gtk_tree_path_new_first();

        if (last_search_path) {
            gtk_tree_path_free(last_search_path);
            last_search_path = NULL;
        }

        search_wrapped = FALSE;
    }

    valid = gtk_tree_model_get_iter(model, &iter, start_path);

    while (valid && !found) {
        if (check_node_for_search_match(model, &iter, search_text)) {
            select_and_scroll_to_found_item(tree_view, model, &iter);
            found = TRUE;
        }

        if (!found)
            valid = gtk_tree_model_iter_next(model, &iter);
    }

    if (!found && find_next && !search_wrapped) {
        search_wrapped = TRUE;
        gtk_tree_path_free(start_path);
        start_path = gtk_tree_path_new_first();
        valid = gtk_tree_model_get_iter(model, &iter, start_path);

        while (valid && !found) {
            GtkTreePath *current_path = gtk_tree_model_get_path(model, &iter);
            if (last_search_path && gtk_tree_path_compare(current_path, last_search_path) >= 0) {
                gtk_tree_path_free(current_path);
                break;
            }
            gtk_tree_path_free(current_path);

            if (check_node_for_search_match(model, &iter, search_text)) {
                select_and_scroll_to_found_item(tree_view, model, &iter);
                found = TRUE;
            }

            if (!found) {
                valid = gtk_tree_model_iter_next(model, &iter);
            }
        }
    }

    search_wrapped = FALSE;
    gtk_tree_path_free(start_path);
    return found;
}

static gboolean reset_search_background(gpointer data) {
#if GTK_CHECK_VERSION(3, 0, 0)
    gtk_widget_override_background_color(GTK_WIDGET(data), GTK_STATE_FLAG_NORMAL, NULL);
#else
    gtk_widget_modify_base(GTK_WIDGET(data), GTK_STATE_NORMAL, NULL);
#endif
    return FALSE; // Run only once
}

static void update_search_widgets_state(void) {
    if (!search_entry || !search_button) return;

    gboolean supported = is_search_supported_for_current_view();

    gtk_widget_set_sensitive(search_entry, supported);
    gtk_widget_set_sensitive(search_button, supported);

    if (supported) {
        gtk_widget_set_tooltip_text(search_entry, _("Enter text to search, double-click to select all"));
    } else {
        gtk_widget_set_tooltip_text(search_entry, _("Search not available"));
    }
}

static gboolean is_search_supported_for_current_view(void) {
    Shell *shell = shell_get_main_shell();

    if (!shell)
        return FALSE;

    return (shell->view_type == SHELL_VIEW_NORMAL
            || shell->view_type == SHELL_VIEW_DUAL
            || shell->view_type == SHELL_VIEW_PROGRESS
            || shell->view_type == SHELL_VIEW_PROGRESS_DUAL
            || shell->view_type == SHELL_VIEW_LOAD_GRAPH);
}

static void on_search_clicked(GtkWidget *widget, gpointer data) {
    if (!search_entry) return;

    const gchar *search_text = gtk_entry_get_text(GTK_ENTRY(search_entry));
    Shell *shell_ptr = shell_get_main_shell();

    if (shell_ptr) {
        if (shell_ptr->view_type == SHELL_VIEW_DETAIL) {
#if GTK_CHECK_VERSION(3, 0, 0)
            GdkRGBA rgba = {1.0, 0.0, 0.0, 0.3};
            gtk_widget_override_background_color(search_entry, GTK_STATE_FLAG_NORMAL, &rgba);
#else
            GdkColor color = {0, 0xFFFF, 0xAAAA, 0xAAAA};
            gtk_widget_modify_base(search_entry, GTK_STATE_NORMAL, &color);
#endif
            g_timeout_add(1000, reset_search_background, search_entry);
            gtk_editable_select_region(GTK_EDITABLE(search_entry), 0, 0);
            return;
        }
    }

    gboolean find_next = FALSE;

    if (current_search_text && search_text &&
        strcmp(current_search_text, search_text) == 0) {
        find_next = TRUE;
    } else {
        if (current_search_text)
            g_free(current_search_text);

        current_search_text = g_strdup(search_text);

        if (last_search_path) {
            gtk_tree_path_free(last_search_path);
            last_search_path = NULL;
        }

        search_wrapped = FALSE;
    }

    if (shell_ptr && search_text && strlen(search_text) > 0) {
        gboolean found = FALSE;

        if (shell_ptr->info_tree && shell_ptr->info_tree->view) {
            found = search_tree_view(shell_ptr->info_tree->view, search_text, find_next);
        }

        if (!found) {
#if GTK_CHECK_VERSION(3, 0, 0)
            GdkRGBA rgba = {1.0, 0.0, 0.0, 0.15};
            gtk_widget_override_background_color(search_entry, GTK_STATE_FLAG_NORMAL, &rgba);
#else
            GdkColor color = {0, 0xFFFF, 0xCCCC, 0xCCCC};
            gtk_widget_modify_base(search_entry, GTK_STATE_NORMAL, &color);
#endif
            g_timeout_add(1000, reset_search_background, search_entry);
        } else {
#if GTK_CHECK_VERSION(3, 0, 0)
            GdkRGBA rgba = {0.0, 1.0, 0.0, 0.1};
            gtk_widget_override_background_color(search_entry, GTK_STATE_FLAG_NORMAL, &rgba);
#else
            GdkColor color = {0, 0xAAAA, 0xFFFF, 0xAAAA};
            gtk_widget_modify_base(search_entry, GTK_STATE_NORMAL, &color);
#endif
            g_timeout_add(1000, reset_search_background, search_entry);
        }

        gtk_editable_select_region(GTK_EDITABLE(search_entry), 0, 0);
    }
}

static gboolean on_search_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) {
    if ((event->keyval == 'f' || event->keyval == 'F') && (event->state & GDK_CONTROL_MASK)) {
        if (search_entry) {
            gtk_widget_grab_focus(search_entry);
            return TRUE;
        }
    }

    return FALSE;
}

static gint info_tree_date_compare(GtkTreeModel *model, GtkTreeIter *a,
                                   GtkTreeIter *b, gpointer user_data)
{
    gint col = GPOINTER_TO_INT(user_data);
    gchar *str_a = NULL, *str_b = NULL;

    gtk_tree_model_get(model, a, col, &str_a, -1);
    gtk_tree_model_get(model, b, col, &str_b, -1);

    time_t ta = parse_boot_date(str_a);
    time_t tb = parse_boot_date(str_b);

    g_free(str_a);
    g_free(str_b);

    if (ta < tb)
        return -1;
    if (ta > tb)
        return 1;

    return 0;
}

Shell *shell_get_main_shell(void)
{
    return shell;
}

void shell_ui_manager_set_visible(const gchar * path, gboolean setting)
{
    GtkWidget *widget;

    if (!params.gui_running)
	return;

    widget = gtk_ui_manager_get_widget(shell->ui_manager, path);
    if (!widget)
	return;

    if (setting)
	gtk_widget_show(widget);
    else
	gtk_widget_hide(widget);
}

void shell_clear_tree_models(Shell *shell)
{
    if(shell){
        if(shell->tree && shell->tree->model) gtk_tree_store_clear(GTK_TREE_STORE(shell->tree->model));
        if(shell->info_tree && shell->info_tree->model) gtk_tree_store_clear(GTK_TREE_STORE(shell->info_tree->model));
    }
}

void shell_clear_timeouts(Shell *shell)
{
    h_hash_table_remove_all(update_tbl);
}

void shell_action_set_property(const gchar * action_name,
			       const gchar * property, gboolean setting)
{
    GtkAction *action;

    if (!params.gui_running)
	return;

    action = gtk_action_group_get_action(shell->action_group, action_name);
    if (action) {
	GValue value = { 0 };

	g_value_init(&value, G_TYPE_BOOLEAN);
	g_value_set_boolean(&value, setting);

	g_object_set_property(G_OBJECT(action), property, &value);

	g_value_unset(&value);
    }
}

void shell_action_set_label(const gchar * action_name, gchar * label)
{
#if GTK_CHECK_VERSION(2,16,0)
  if (params.gui_running && shell->action_group) {
	GtkAction *action;

	action = gtk_action_group_get_action(shell->action_group, action_name);
	if (action) gtk_action_set_label(action, label);
    }
#endif
}

void shell_action_set_enabled(const gchar * action_name, gboolean setting)
{
  if (params.gui_running && shell->action_group) {
	GtkAction *action;

	action = gtk_action_group_get_action(shell->action_group, action_name);
	if (action) gtk_action_set_sensitive(action, setting);
    }
}

gboolean shell_action_get_enabled(const gchar * action_name)
{
    GtkAction *action;

    if (!params.gui_running)
	return FALSE;

    action = gtk_action_group_get_action(shell->action_group, action_name);
    if (action) return gtk_action_get_sensitive(action);

    return FALSE;
}

void shell_set_side_pane_visible(gboolean setting)
{
    if (!params.gui_running)
	return;

    if (setting)
	gtk_widget_show(shell->tree->scroll);
    else
	gtk_widget_hide(shell->tree->scroll);
}

gboolean shell_action_get_active(const gchar * action_name)
{
    GtkAction *action;
    GSList *proxies;

    if (!params.gui_running) return FALSE;

    action = gtk_action_group_get_action(shell->action_group, action_name);
    if (action) {
	proxies = gtk_action_get_proxies(action);

	for (; proxies; proxies = proxies->next) {
	    GtkWidget *widget = (GtkWidget *) proxies->data;

	    if (GTK_IS_CHECK_MENU_ITEM(widget)) {
		return
		    gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM
						   (widget));
	    }
	}
    }

    return FALSE;
}

void shell_action_set_active(const gchar * action_name, gboolean setting)
{
    GtkAction *action;
    GSList *proxies;

    if (!params.gui_running) return;

    action = gtk_action_group_get_action(shell->action_group, action_name);
    if (action) {
	proxies = gtk_action_get_proxies(action);

	for (; proxies; proxies = proxies->next) {
	    GtkWidget *widget = (GtkWidget *) proxies->data;

	    if (GTK_IS_CHECK_MENU_ITEM(widget)) {
		gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget),
					       setting);
		return;
	    }
	}
    }
}

void shell_status_pulse(void)
{
    if (params.gui_running) {
	if(shell && shell->selected_module && strcmp(shell->selected_module->name,_("Benchmark")))
	    if(gtk_widget_get_visible(shell->progress)) gtk_progress_bar_pulse(GTK_PROGRESS_BAR(shell->progress));
	int ii=5;while(ii-- && gtk_events_pending() && !gtk_main_iteration_do(FALSE)) {;}

    } else if (!params.quiet) {
	static gint counter = 0;

	fprintf(stderr, "\033[2K\033[40;37;1m %c\033[0m\r",
		"|/-\\"[counter++ % 4]);
    }
}

void shell_status_set_percentage(gint percentage)
{
    if (params.gui_running) {
	if(gtk_widget_get_visible(shell->progress)) gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(shell->progress), (float) percentage / 100.0);
    } else if (!params.quiet) {
	if (percentage < 1 || percentage >= 100) {
	    fprintf(stderr, "\033[2K");
	} else {
	    gchar pbar[] = "----------";

	    memset(pbar, '#', percentage / 10);

	    fprintf(stderr, "\r\033[40;37;1m%3d%% \033[40;34;1m"
		    "%s\033[0m\r", percentage, pbar);
	}
    }
}

void shell_view_set_enabled(gboolean setting)
{
    if (!params.gui_running)
	return;
    if (setting) {
	shell->_pulses = 0;
	widget_set_cursor(GTK_WIDGET(shell->window), GDK_LEFT_PTR);
    } else {
	widget_set_cursor(GTK_WIDGET(shell->window), GDK_WATCH);
    }

    gtk_widget_set_sensitive(shell->hbox, setting);
    shell_action_set_enabled("ViewMenuAction", setting);
    shell_action_set_enabled("RefreshAction", setting);
    //shell_action_set_enabled("CopyAction", setting);
    shell_action_set_enabled("ReportAction", setting);
    shell_action_set_enabled("SyncManagerAction", setting);
    int ii=5;while(ii-- && gtk_events_pending() && !gtk_main_iteration_do(FALSE)) {;}
    updating=!setting;
    if(!updating && closing) cb_quit();
}

void shell_status_set_enabled(gboolean setting)
{
    if (!params.gui_running)
	return;

    shell_view_set_enabled(!setting);
    if (setting)
        gtk_widget_show(shell->progress);
    else {
        gtk_widget_hide(shell->progress);
	shell_status_update(_("Done."));
    }
}

void shell_do_reload(gboolean reload)
{
    if (!params.gui_running || !shell->selected)
	return;

    shell_action_set_enabled("RefreshAction", FALSE);
    //shell_action_set_enabled("CopyAction", FALSE);
    shell_action_set_enabled("ReportAction", FALSE);

    shell_status_set_enabled(TRUE);

    params.aborting_benchmarks=0;
    if(reload) module_entry_reload(shell->selected);
    if(!params.aborting_benchmarks) {
        module_selected(NULL);
    } else {
        shell_status_set_enabled(FALSE);
    }

    params.aborting_benchmarks=0;

    shell_action_set_enabled("RefreshAction", TRUE);
    //shell_action_set_enabled("CopyAction", TRUE);
    shell_action_set_enabled("ReportAction", TRUE);
}

void shell_status_update(const gchar * message)
{
    if (params.gui_running) {
        if(gtk_widget_get_visible(shell->status)) gtk_label_set_markup(GTK_LABEL(shell->status), message);
    } else if (!params.quiet) {
	fprintf(stderr, "\033[2K\033[40;37;1m %s\033[0m\r", message);
    }
}

gboolean destroy_event (GtkWidget* self, GdkEvent* event, gpointer user_data) {
    if(!updating){
        return FALSE;
    } else {
        closing=TRUE;
        return TRUE;
    }
}
static void destroy_me(void){
    cb_quit();
}

#if GTK_CHECK_VERSION(3, 0, 0)
int update=0;
int newgnome=0;
gchar *ng_theme=NULL,*og_theme=NULL,*ogi_theme=NULL;
static gboolean stylechange_in_progress = FALSE;
static void stylechange2_me(void)
{
  if(update>=1){
    GtkStyleContext *sctx=gtk_widget_get_style_context(GTK_WIDGET(shell->window));
    GdkRGBA color;
    gtk_style_context_lookup_color(sctx, "theme_text_color", &color);
    gint darkmode=0;
    if((color.red+color.green+color.blue)>=1.5) darkmode=1;
    //
    if((update==2) && params.darkmode && (!darkmode) ) {
        if(newgnome){
            GtkSettings *set;
	    set=gtk_settings_get_default();
	    g_object_set(set,"gtk-theme-name","HighContrastInverse", NULL);
	    //update theme
	    cb_disable_theme();
	    gtk_style_context_lookup_color(sctx, "theme_text_color", &color);
	    darkmode=0;
	    if((color.red+color.green+color.blue)>=1.5) darkmode=1;
	}
    }
    if((update==2) && !params.darkmode && darkmode){
        if(newgnome){
            GtkSettings *set;
	    set=gtk_settings_get_default();
	    g_object_set(set,"gtk-theme-name","Adwaita", NULL);
	    //update theme
	    cb_disable_theme();
	    gtk_style_context_lookup_color(sctx, "theme_text_color", &color);
	    darkmode=0;
	    if((color.red+color.green+color.blue)>=1.5) darkmode=1;
	}
    }
    //Wrong dark/light fixing
    if((darkmode!=params.darkmode) && (update==1)){
        params.darkmode=darkmode;
        //update theme
        cb_disable_theme();
    }
  }
  if(update) update--;
}

//gsettings
static void stylechange_signal(void)
{
    if (stylechange_in_progress) return;
    stylechange_in_progress = TRUE;
    int newDark=-1;
    //new gnome using only normal/dark mode
    if(settings){
        //FIXME get dynamic scaling info
        //scale=g_settings_get_string(settings, "text-scaling-factor");
	//if(scale) sscanf(scale,"%f",&params.scale);

        if(newgnome){
            gchar *theme = g_settings_get_string(settings, "color-scheme");
	    if(ng_theme && theme && strcmp(ng_theme, theme)){
	        if(ng_theme) g_free(ng_theme);
	        ng_theme=theme;
                if(strstr(theme,"Dark")||strstr(theme,"dark")) newDark=1;
                if(strstr(theme,"Light")||strstr(theme,"light")||strstr(theme,"default")) newDark=0;
	    } else g_free(theme);
	}
	//older gnome using themes with dark in theme-name
	if(newDark==-1){
	    gchar *theme = g_settings_get_string(settings, "gtk-theme");
	    if(og_theme && theme && strcmp(og_theme, theme)){
	        newDark=0;
		if(og_theme) g_free(og_theme);
		og_theme=theme;
		if(strstr(theme,"Dark")||strstr(theme,"dark")) {newDark=1;}
	    } else g_free(theme);
        }
	//older gnome using themes with dark in theme-name
	if(newDark==-1){
	    gchar *theme = g_settings_get_string(settings, "icon-theme");
	    if(ogi_theme && theme && strcmp(ogi_theme, theme)){
	        newDark=0;
		if(ogi_theme) g_free(ogi_theme);
		ogi_theme=theme;
		if(strstr(theme,"Dark")||strstr(theme,"dark")) {newDark=1;}
	    } else g_free(theme);
        }
    }
    if(newDark==1){
        if(params.darkmode!=1) {
	    params.darkmode=1;
            update=2;
        }
    }else if(newDark==0) {
        if(params.darkmode!=0) {
	    params.darkmode=0;
            update=2;
        }
    }
    if(newDark!=-1){
        shell_do_reload(false);
        stylechange2_me();
    }
    stylechange_in_progress = FALSE;
}

//GTK-signal
static void stylechange_updated(void)
{
    if (stylechange_in_progress) return;
    if(!update) update=1;
    stylechange2_me();
}
#endif

static void close_note(GtkWidget * widget, gpointer user_data)
{
    gtk_widget_hide(shell->note->event_box);
}

static ShellNote *note_new(void)
{
    ShellNote *note;
    GtkWidget *hbox, *icon, *button;
    GtkWidget *border_box;
    GdkColor info_default_border_color     = { 0, 0x0000, 0xad00, 0x9d00 };

    note = g_new0(ShellNote, 1);
    note->label = gtk_label_new("");
    note->event_box = gtk_event_box_new();
    button = gtk_button_new();

    border_box = gtk_event_box_new();
    gtk_container_set_border_width(GTK_CONTAINER(border_box), 1);
    gtk_container_add(GTK_CONTAINER(note->event_box), border_box);
    gtk_widget_show(border_box);

#if GTK_CHECK_VERSION(3, 0, 0)
    GdkRGBA info_default_text_color       = { .red = 1, .green = 1, .blue = 1, .alpha = 1.0 };
    gtk_widget_override_color(note->label, GTK_STATE_FLAG_NORMAL, &info_default_text_color);
#else
    GdkColor info_default_text_color       = { 0, 0xffff, 0xffff, 0xffff };
    gtk_widget_modify_fg(note->label, GTK_STATE_NORMAL, &info_default_text_color);
#endif
    if(params.darkmode){
        GdkColor info_default_fill_color = { 0, 0x4000*1.1, 0x6000*1.1, 0xff00 };
	gtk_widget_modify_bg(border_box, GTK_STATE_NORMAL, &info_default_fill_color);
    } else {
        GdkColor info_default_fill_color = { 0, 0x4000*0.9, 0x6000*0.9, 0xff00 };
	gtk_widget_modify_bg(border_box, GTK_STATE_NORMAL, &info_default_fill_color);
    }
    gtk_widget_modify_bg(note->event_box, GTK_STATE_NORMAL, &info_default_border_color);

    icon = icon_cache_get_image_at_size("close.svg", 16, 16);
    gtk_widget_show(icon);
    gtk_container_add(GTK_CONTAINER(button), icon);
    gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
    g_signal_connect(G_OBJECT(button), "clicked", (GCallback) close_note,
		     NULL);

#if GTK_CHECK_VERSION(3, 0, 0)
    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3);
#else
    hbox = gtk_hbox_new(FALSE, 3);
#endif
    icon = icon_cache_get_image("dialog-information.svg");

    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 4);
    gtk_box_pack_start(GTK_BOX(hbox), note->label, FALSE, FALSE, 0);
    gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);

    gtk_container_set_border_width(GTK_CONTAINER(hbox), 2);
    gtk_container_add(GTK_CONTAINER(border_box), hbox);
    gtk_widget_show_all(hbox);

    return note;
}

void shell_set_title(Shell *shell, gchar *subtitle)
{
    if (subtitle) {
        gchar *tmp;

        tmp = g_strdup_printf(_("%s - System Information and Benchmark"), subtitle);
        gtk_window_set_title(GTK_WINDOW(shell->window), tmp);

        g_free(tmp);
    } else {
        gtk_window_set_title(GTK_WINDOW(shell->window), _("System Information and Benchmark"));
    }
}

static void create_window(void)
{
    GtkWidget *vbox, *hbox;

    shell = g_new0(Shell, 1);

    shell->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_icon(GTK_WINDOW(shell->window), icon_cache_get_pixbuf("hardinfo2.svg"));
    shell_set_title(shell, NULL);
    gtk_window_set_default_size(GTK_WINDOW(shell->window), 1280*params.scale, 800*params.scale);
    g_signal_connect(G_OBJECT(shell->window), "destroy", G_CALLBACK(destroy_me), NULL);
    g_signal_connect(G_OBJECT(shell->window), "delete-event", G_CALLBACK(destroy_event), NULL);
#if GTK_CHECK_VERSION(3, 0, 0)
    //Get Darkmode
    GtkStyleContext *sctx=gtk_widget_get_style_context(GTK_WIDGET(shell->window));
    GdkRGBA color;
    gtk_style_context_lookup_color(sctx, "theme_text_color", &color);
    if((color.red+color.green+color.blue)>=1.5) params.darkmode=1;
    //
    g_signal_connect(G_OBJECT(shell->window), "style-updated", stylechange_updated, NULL);
    if(g_settings_schema_source_lookup(g_settings_schema_source_get_default(),"org.gnome.desktop.interface",FALSE))
        settings=g_settings_new("org.gnome.desktop.interface");
    if(settings) g_signal_connect_after(settings,"changed",stylechange_signal,NULL);
    const gchar *env=g_getenv("XDG_CURRENT_DESKTOP");
    //Cinnamon has color-scheme but does not update it depending on dark/light/default
    if(!env || !strstr(env,"innamon"))
      if(settings) {//get settings about newgnome
        gchar **keys=NULL;
	int i=0;
	if(!newgnome) keys=g_settings_list_keys(settings);
	while(!newgnome && keys && (keys[i]!=NULL)){
	  if(strcmp(keys[i],"color-scheme")==0) newgnome=1;
	  i++;
	}
	if(keys) g_strfreev(keys);
	//register settings at startup
	if(newgnome) {
	    ng_theme = g_settings_get_string(settings, "color-scheme");
	    if(strstr(ng_theme,"Dark")||strstr(ng_theme,"dark")) {params.darkmode=1;update=2;}
	}
	og_theme = g_settings_get_string(settings, "gtk-theme");//normal
	ogi_theme = g_settings_get_string(settings, "icon-theme");//alternate
    }
#endif

#if GTK_CHECK_VERSION(3, 0, 0)
    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    vbox = gtk_vbox_new(FALSE, 0);
#endif
    gtk_widget_show(vbox);
    gtk_container_add(GTK_CONTAINER(shell->window), vbox);
    shell->vbox = vbox;

    menu_init(shell);

    g_signal_connect(G_OBJECT(shell->window), "key-press-event", G_CALLBACK(on_search_key_press), NULL);

    GtkWidget *toolbar = gtk_ui_manager_get_widget(shell->ui_manager, "/MainMenuBarAction");

    if (toolbar && GTK_IS_TOOLBAR(toolbar)) {
        GtkToolItem *separator = gtk_separator_tool_item_new();
        gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(separator), FALSE);
        gtk_tool_item_set_expand(separator, TRUE);
        gtk_toolbar_insert(GTK_TOOLBAR(toolbar), separator, -1);
        gtk_widget_show(GTK_WIDGET(separator));

        search_entry = gtk_entry_new();
        gtk_widget_set_size_request(search_entry, 150, -1);
        GtkToolItem *entry_item = gtk_tool_item_new();
        gtk_container_add(GTK_CONTAINER(entry_item), search_entry);
        gtk_toolbar_insert(GTK_TOOLBAR(toolbar), entry_item, -1);
        gtk_widget_show_all(GTK_WIDGET(entry_item));
        g_signal_connect(G_OBJECT(search_entry), "activate", G_CALLBACK(on_search_clicked), NULL);

        search_button = gtk_button_new_with_label(_("Search"));
        GtkToolItem *button_item = gtk_tool_item_new();
        gtk_container_add(GTK_CONTAINER(button_item), search_button);
        gtk_toolbar_insert(GTK_TOOLBAR(toolbar), button_item, -1);
        gtk_widget_show_all(GTK_WIDGET(button_item));
        g_signal_connect(G_OBJECT(search_button), "clicked", G_CALLBACK(on_search_clicked), NULL);

        update_search_widgets_state();
    }

#if GTK_CHECK_VERSION(3, 0, 0)
    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
#else
    hbox = gtk_hbox_new(FALSE, 5);
#endif
    gtk_widget_show(hbox);
    gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);

    shell->progress = gtk_progress_bar_new();
#if GTK_CHECK_VERSION(3, 0, 0)
    gtk_widget_set_valign(GTK_WIDGET(shell->progress), GTK_ALIGN_CENTER);
#else
    //Results in conversion error - just disable for GTK2
#endif
    gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(shell->progress),0.10);
    gtk_widget_hide(shell->progress);
    gtk_box_pack_end(GTK_BOX(hbox), shell->progress, FALSE, FALSE, 5);

    shell->status = gtk_label_new("Starting...");
#if GTK_CHECK_VERSION(3, 0, 0)
    gtk_widget_set_valign(GTK_WIDGET(shell->status), GTK_ALIGN_CENTER);
#else
    gtk_misc_set_alignment(GTK_MISC(shell->status), 0.0, 0.5);
#endif
    gtk_widget_show(shell->status);
    gtk_box_pack_start(GTK_BOX(hbox), shell->status, FALSE, FALSE, 5);

#if GTK_CHECK_VERSION(3, 0, 0)
    shell->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
#else
    shell->hbox = gtk_hbox_new(FALSE, 5);
#endif
    gtk_widget_show(shell->hbox);
    gtk_box_pack_end(GTK_BOX(vbox), shell->hbox, TRUE, TRUE, 0);

#if GTK_CHECK_VERSION(3, 0, 0)
    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
#else
    vbox = gtk_vbox_new(FALSE, 5);
#endif
    gtk_widget_show(vbox);
    gtk_box_pack_end(GTK_BOX(shell->hbox), vbox, TRUE, TRUE, 0);

    shell->note = note_new();
    gtk_box_pack_end(GTK_BOX(vbox), shell->note->event_box, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3, 0, 0)
    shell->vpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
#else
    shell->vpaned = gtk_vpaned_new();
#endif
    gtk_box_pack_start(GTK_BOX(vbox), shell->vpaned, TRUE, TRUE, 0);
    gtk_widget_show(shell->vpaned);

    shell->notebook = gtk_notebook_new();
    gtk_paned_add2(GTK_PANED(shell->vpaned), shell->notebook);

    GKeyFile *key_file = g_key_file_new();
    gchar *conf_path = g_build_filename(g_get_user_config_dir(), "hardinfo2","settings.ini", NULL);
    g_key_file_load_from_file(key_file, conf_path, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
    params.theme = g_key_file_get_integer(key_file, "Theme", "ThemeNumber", NULL);
    if(params.theme==0) params.theme=1;
    if(params.theme<-1) params.theme=-1;
    if(params.theme>6) params.theme=-1;

    g_free(conf_path);
    g_key_file_free(key_file);
#if GTK_CHECK_VERSION(3, 0, 0)
    if(params.theme==-1) shell_action_set_active("DisableThemeAction", TRUE);
    if(params.theme==1) shell_action_set_active("Theme1Action", TRUE);
    if(params.theme==2) shell_action_set_active("Theme2Action", TRUE);
    if(params.theme==3) shell_action_set_active("Theme3Action", TRUE);
    if(params.theme==4) shell_action_set_active("Theme4Action", TRUE);
    if(params.theme==5) shell_action_set_active("Theme5Action", TRUE);
    if(params.theme==6) shell_action_set_active("Theme6Action", TRUE);
#endif

    gtk_widget_show(shell->window);

    g_timeout_add(100, (GSourceFunc)update_search_widgets_state, NULL);

    //Check packaging
    gchar *pkgok=NULL,*p;
    //minimum of packages installed by hardinfo2 package - as specified on README.md
    if(!check_program("awk"))                                     {p=pkgok;pkgok=g_strconcat("gawk\n", pkgok, NULL);g_free(p);}
    if(!check_program("dmidecode"))                               {p=pkgok;pkgok=g_strconcat("dmidecode\n", pkgok, NULL);g_free(p);}
    if(!check_program("xdg-open"))                                {p=pkgok;pkgok=g_strconcat("xdg-open / xdg-utils\n", pkgok, NULL);g_free(p);}
    if(strstr(PACK_REQ,"udisk") && !check_program("udisksctl"))   {p=pkgok;pkgok=g_strconcat("udisk2\n", pkgok, NULL);g_free(p);}
    if(strstr(PACK_REQ,"vulkan") && !check_program("vulkaninfo")) {p=pkgok;pkgok=g_strconcat("vulkaninfo / vulkan-tools\n", pkgok, NULL);g_free(p);}
    if(!check_program("glxinfo"))                                 {p=pkgok;pkgok=g_strconcat("glxinfo / mesa-utils\n", pkgok, NULL);g_free(p);}
    if(strstr(PACK_REQ,"iperf") && !check_program("iperf3"))      {p=pkgok;pkgok=g_strconcat("iperf3\n", pkgok, NULL);g_free(p);}
    if(strstr(PACK_REQ,"sysbench") && !check_program("sysbench")) {p=pkgok;pkgok=g_strconcat("sysbench\n", pkgok, NULL);g_free(p);}
    //no binary in qt5/6-base package
    //randr optional
    //if(strstr(PACK_REQ,"randr") && !check_program("xrandr"))      {p=pkgok;pkgok=g_strconcat("xrandr\n", pkgok, NULL);g_free(p);}
    //fwupd optional
    //if(strstr(PACK_REQ,"fwupd") && !check_program("fwupdtool") && !check_program("fwupdmgr") ) {p=pkgok;pkgok=g_strconcat("fwupd\n", pkgok, NULL);g_free(p);}
    if(pkgok){
        GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
	GtkWidget *pkgdialog;
        pkgdialog = gtk_message_dialog_new_with_markup (GTK_WINDOW(shell->window),
			 flags,
			 GTK_MESSAGE_ERROR,
			 GTK_BUTTONS_CLOSE,
			 _("<b>Hardinfo2 not packaged correctly</b>\n\nMissing packages:\n\n%s\n\nPlease fix by installing manually"),
			 pkgok);
        gtk_dialog_run (GTK_DIALOG (pkgdialog));
        gtk_widget_destroy (pkgdialog);
	g_free(pkgok);
    }
}


void shell_add_modules_to_gui(gpointer _shell_module, gpointer _shell_tree)
{
    ShellModule *module = (ShellModule *) _shell_module;
    ShellTree *shelltree = (ShellTree *) _shell_tree;
    GtkTreeStore *store = GTK_TREE_STORE(shelltree->model);
    GtkTreeIter parent;

    if (!module) {
        return;
    }

    gtk_tree_store_append(store, &parent, NULL);
    gtk_tree_store_set(store, &parent,
                       TREE_COL_NAME, module->name,
		       TREE_COL_MODULE, module,
		       TREE_COL_MODULE_ENTRY, NULL,
		       TREE_COL_SEL, FALSE,
		       -1);

    if (module->icon) {
	gtk_tree_store_set(store, &parent, TREE_COL_PBUF, module->icon,
			   -1);
    }

    if (module->entries) {
	ShellModuleEntry *entry;
	GSList *p;

	for (p = module->entries; p; p = g_slist_next(p)) {
	    GtkTreeIter child;
	    entry = (ShellModuleEntry *) p->data;
        if (entry->flags & MODULE_FLAG_HIDE) continue;

	    gtk_tree_store_append(store, &child, &parent);
	    gtk_tree_store_set(store, &child, TREE_COL_NAME, entry->name,
			       TREE_COL_MODULE_ENTRY, entry,
			       TREE_COL_SEL, FALSE, -1);

	    if (entry->icon) {
		gtk_tree_store_set(store, &child, TREE_COL_PBUF,
				   entry->icon, -1);
	    }

	    shell_status_pulse();
	}

    }
}

static void destroy_update_tbl_value(gpointer data)
{
    struct UpdateTableItem *item = (struct UpdateTableItem *)data;

    if (item->is_iter) {
        gtk_tree_iter_free(item->iter);
    } else {
        g_object_unref(item->widget);
    }

    g_free(item);
}

static void append_row_to_clipboard_text(GString *clipboard_text, GtkTreeView *treeview,
                                          const gchar *name, const gchar *value,
                                          const gchar *extra1, const gchar *extra2,
                                          gdouble progress) {
    Shell *shell = shell_get_main_shell();
    GtkTreeViewColumn *column;
    gchar *progress_text;
    struct {
        const gchar *text;
        gboolean visible;
        int column_id;
    } columns[5];
    int column_count = 0;

    if (value && *value) {
        progress_text = g_strdup(value);
    } else {
        /* fallback: format value of progress */
        progress_text = g_strdup_printf("%.1f%%", progress);
    }

    if (shell && shell->info_tree && treeview) {
        GList *columns_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(treeview));
        GList *iter;

        for (iter = columns_list; iter; iter = iter->next) {
            column = GTK_TREE_VIEW_COLUMN(iter->data);

            if (column && gtk_tree_view_column_get_visible(column)) {
                if (column == shell->info_tree->col_progress) {
                    columns[column_count].text = progress_text;
                    columns[column_count].visible = TRUE;
                    columns[column_count].column_id = INFO_TREE_COL_PROGRESS;
                    column_count++;
                } else if (column == shell->info_tree->col_textvalue) {
                    columns[column_count].text = name;
                    columns[column_count].visible = TRUE;
                    columns[column_count].column_id = INFO_TREE_COL_NAME;
                    column_count++;
                } else if (column == shell->info_tree->col_value) {
                    columns[column_count].text = value;
                    columns[column_count].visible = TRUE;
                    columns[column_count].column_id = INFO_TREE_COL_VALUE;
                    column_count++;
                } else if (column == shell->info_tree->col_extra1) {
                    columns[column_count].text = extra1;
                    columns[column_count].visible = TRUE;
                    columns[column_count].column_id = INFO_TREE_COL_EXTRA1;
                    column_count++;
                } else if (column == shell->info_tree->col_extra2) {
                    columns[column_count].text = extra2;
                    columns[column_count].visible = TRUE;
                    columns[column_count].column_id = INFO_TREE_COL_EXTRA2;
                    column_count++;
                }
            }
        }

        g_list_free(columns_list);
    } else {
        columns[0].text = progress_text;
        columns[0].visible = !!progress_text;
        columns[0].column_id = INFO_TREE_COL_PROGRESS;

        columns[1].text = name;
        columns[1].visible = !!name;
        columns[1].column_id = INFO_TREE_COL_NAME;

        columns[2].text = value;
        columns[2].visible = !!value;
        columns[2].column_id = INFO_TREE_COL_VALUE;

        columns[3].text = extra1;
        columns[3].visible = !!extra1;
        columns[3].column_id = INFO_TREE_COL_EXTRA1;

        columns[4].text = extra2;
        columns[4].visible = !!extra2;
        columns[4].column_id = INFO_TREE_COL_EXTRA2;

        column_count = 5;
    }

    gboolean first = TRUE;
    gint i;

    /* append text with display order */
    for (i = 0; i < column_count; i++) {
        if (columns[i].visible && columns[i].text) {
            if (!first)
                g_string_append(clipboard_text, "\t");

            g_string_append(clipboard_text, columns[i].text);
            first = FALSE;
        }

        if (columns[i].column_id == INFO_TREE_COL_PROGRESS && columns[i].text) {
            g_free((gchar*)columns[i].text);
        }
    }
}

static void copy_rows_to_clipboard(GtkTreeView *treeview, gboolean copy_all) {
    GtkTreeModel *model = gtk_tree_view_get_model(treeview);
    GString *clipboard_text = g_string_new("");
    GtkTreeIter iter;
    gboolean valid = FALSE;

    if (copy_all) {
        valid = gtk_tree_model_get_iter_first(model, &iter);
    } else {
        GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
        valid = gtk_tree_selection_get_selected(selection, &model, &iter);
    }

    gboolean first = TRUE;

    do {
        if (!valid)
            break;

        gchar *name = NULL, *value = NULL, *extra1 = NULL, *extra2 = NULL;
        gdouble progress = 0.0;
        gtk_tree_model_get(model, &iter,
                            INFO_TREE_COL_NAME, &name,
                            INFO_TREE_COL_VALUE, &value,
                            INFO_TREE_COL_EXTRA1, &extra1,
                            INFO_TREE_COL_EXTRA2, &extra2,
                            INFO_TREE_COL_PROGRESS, &progress,
                            -1);

        if (!first)
            g_string_append(clipboard_text, "\n");

        append_row_to_clipboard_text(clipboard_text, treeview, name, value, extra1, extra2, progress);

        g_free(name);
        g_free(value);
        g_free(extra1);
        g_free(extra2);

        first = FALSE;

        if (copy_all) {
            valid = gtk_tree_model_iter_next(model, &iter);
        } else {
            valid = FALSE;
        }
    } while (valid);

    GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
    gtk_clipboard_set_text(clipboard, clipboard_text->str, -1);

    g_string_free(clipboard_text, TRUE);
}

static gchar* remove_html_tags(const gchar *text) {
    if (!text)
        return NULL;

    GString *result = g_string_new("");
    gboolean in_tag = FALSE;

    gint i = 0;
    while (text[i]) {
        if (text[i] == '<') {
            in_tag = TRUE;
        } else if (text[i] == '>') {
            in_tag = FALSE;
        } else if (!in_tag) {
            g_string_append_c(result, text[i]);
        }
	i++;
    }

    return g_string_free(result, FALSE);
}

/* sort by position */
static gint compare_widget_positions(gconstpointer a, gconstpointer b) {
    const PositionedWidget *pa = (const PositionedWidget *)a;
    const PositionedWidget *pb = (const PositionedWidget *)b;

    /* sort from top to bottom */
    if (pa->y != pb->y)
        return pa->y - pb->y;

    /* sort from left to right */
    return pa->x - pb->x;
}

static void collect_labels_with_positions(GtkWidget *widget, GList **labels, GtkContainer *toplevel) {
    if (GTK_IS_LABEL(widget)) {
        /* get position relative to the detail view container */
        gint x, y;

        if (gtk_widget_translate_coordinates(widget, GTK_WIDGET(toplevel), 0, 0, &x, &y)) {
            PositionedWidget *pw = g_new0(PositionedWidget, 1);
            pw->widget = widget;
            pw->x = x;
            pw->y = y;
            *labels = g_list_prepend(*labels, pw);
        }
    } else if (GTK_IS_CONTAINER(widget)) {
        /* process recursively */
        GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
        GList *iter = children;

        while (iter) {
            collect_labels_with_positions(GTK_WIDGET(iter->data), labels, toplevel);
            iter = iter->next;
        }

        g_list_free(children);
    }
}

static void copy_detail_view_to_clipboard(GtkWidget *menu_item, gpointer user_data) {
    DetailView *detail_view = (DetailView *)user_data;
    GString *clipboard_text = g_string_new("");
    gboolean first = TRUE;
    /* collect_labels_with_positions may not change this, so it should be NULL */
    GList *positioned_labels = NULL;
    GList *iter;

    collect_labels_with_positions(GTK_WIDGET(detail_view->view), &positioned_labels, GTK_CONTAINER(detail_view->view));
    /* sort by position (top to bottom, left to right) */
    positioned_labels = g_list_sort(positioned_labels, compare_widget_positions);

    /* get text by position in order */
    for (iter = positioned_labels; iter; iter = iter->next) {
        PositionedWidget *pw = (PositionedWidget *)iter->data;

        if (GTK_IS_LABEL(pw->widget)) {
            gchar *text = NULL;
            g_object_get(pw->widget, "label", &text, NULL);

            if (text && *text) {
                /* remove HTML <> */
                gchar *clean_text = remove_html_tags(text);

                if (clean_text && *clean_text) {
                    if (!first)
                        g_string_append(clipboard_text, "\n");

                    g_string_append(clipboard_text, clean_text);
                    first = FALSE;
                }

                g_free(clean_text);
            }

            g_free(text);
        }

        g_free(pw);
    }

    g_list_free(positioned_labels);

    GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
    gtk_clipboard_set_text(clipboard, clipboard_text->str, -1);
    g_string_free(clipboard_text, TRUE);
}

static void copy_selected_row_to_clipboard(GtkWidget *menu_item, gpointer user_data) {
    GtkTreeView *treeview = GTK_TREE_VIEW(user_data);
    copy_rows_to_clipboard(treeview, FALSE);
}

static void copy_all_rows_to_clipboard(GtkWidget *menu_item, gpointer user_data) {
    GtkTreeView *treeview = GTK_TREE_VIEW(user_data);
    copy_rows_to_clipboard(treeview, TRUE);
}

static gboolean on_info_tree_popup_menu(GtkWidget *treeview, GdkEventButton *event, gpointer user_data) {
    if (event->type == GDK_BUTTON_PRESS && event->button == 3) { // right click button
        GtkWidget *menu = gtk_menu_new();
        GtkWidget *copy_row_item = gtk_menu_item_new_with_label(_("Copy selected row"));
        GtkWidget *copy_all_item = gtk_menu_item_new_with_label(_("Copy all rows"));

        GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
        gboolean has_selection = gtk_tree_selection_get_selected(selection, NULL, NULL);

        /* disable copy row item if no selection */
        gtk_widget_set_sensitive(copy_row_item, has_selection);
        g_signal_connect(copy_row_item, "activate", G_CALLBACK(copy_selected_row_to_clipboard), treeview);
        g_signal_connect(copy_all_item, "activate", G_CALLBACK(copy_all_rows_to_clipboard), treeview);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy_row_item);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy_all_item);
        gtk_widget_show_all(menu);

#if GTK_CHECK_VERSION(3, 22, 0)
        gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent*)event);
#else
        gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
#endif
        return TRUE;
    }

    return FALSE;
}

static gboolean on_detail_view_popup_menu(GtkWidget *widget, GdkEventButton *event, gpointer user_data) {
    if (event->type == GDK_BUTTON_PRESS && event->button == 3) { // right click button
        GtkWidget *menu = gtk_menu_new();
        GtkWidget *copy_all_item = gtk_menu_item_new_with_label(_("Copy all text"));

        g_signal_connect(copy_all_item, "activate", G_CALLBACK(copy_detail_view_to_clipboard), user_data);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy_all_item);
        gtk_widget_show_all(menu);
#if GTK_CHECK_VERSION(3, 22, 0)
        gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent*)event);
#else
        gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
#endif
        return TRUE;
    }

    return FALSE;
}

/* disable context menus recursively */
static void disable_context_menus(GtkWidget *widget) {
    if (GTK_IS_LABEL(widget)) {
        gtk_label_set_selectable(GTK_LABEL(widget), FALSE);
    }

    /* process recursively if a container */
    if (GTK_IS_CONTAINER(widget)) {
        GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
        GList *iter = children;
        while (iter) {
            GtkWidget *child = GTK_WIDGET(iter->data);
            disable_context_menus(child);
            iter = iter->next;
        }
        g_list_free(children);
    }
}

DetailView *detail_view_new(void)
{
    DetailView *detail_view;

    detail_view = g_new0(DetailView, 1);
    detail_view->scroll = gtk_scrolled_window_new(NULL, NULL);
#if GTK_CHECK_VERSION(3, 0, 0)
    detail_view->view = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    detail_view->view = gtk_vbox_new(FALSE, 0);
#endif

    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(detail_view->scroll),
                                        GTK_SHADOW_IN);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(detail_view->scroll),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
#if GTK_CHECK_VERSION(3, 0, 0)
    gtk_container_add(GTK_CONTAINER(detail_view->scroll), detail_view->view);
#else
    gtk_scrolled_window_add_with_viewport(
        GTK_SCROLLED_WINDOW(detail_view->scroll), detail_view->view);
#endif

    /* enable right-click context menu for detail view */
    gtk_widget_add_events(detail_view->scroll, GDK_BUTTON_PRESS_MASK);
    g_signal_connect(detail_view->scroll, "button-press-event", G_CALLBACK(on_detail_view_popup_menu), detail_view);
    gtk_widget_add_events(detail_view->view, GDK_BUTTON_PRESS_MASK);
    g_signal_connect(detail_view->view, "button-press-event", G_CALLBACK(on_detail_view_popup_menu), detail_view);

    gtk_widget_show_all(detail_view->scroll);

    return detail_view;
}

static gboolean select_first_tree_item(gpointer data)
{
    GtkTreeIter first;

    if (gtk_tree_model_get_iter_first(shell->tree->model, &first))
        gtk_tree_selection_select_iter(shell->tree->selection, &first);

    return FALSE;
}

static gboolean select_marked_or_first_item(gpointer data)
{
    GtkTreeIter first, it;
    gboolean found_selection = FALSE;
    gchar *datacol;
    GtkTreeModel *sort_model = shell->info_tree->sort_model;

    if (gtk_tree_model_get_iter_first(sort_model, &first)) {
        it = first;
        do {
            gtk_tree_model_get(sort_model, &it, INFO_TREE_COL_DATA, &datacol, -1);

            if (key_is_highlighted(datacol)) {
                gtk_tree_selection_select_iter(shell->info_tree->selection, &it);

                /* scroll to selected item */
                GtkTreePath *path = gtk_tree_model_get_path(sort_model, &it);
                gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(shell->info_tree->view), path, NULL, TRUE, 0.5, 0.0);
                gtk_tree_path_free(path);

                found_selection = TRUE;
                g_free(datacol);
                return FALSE;
            }
            g_free(datacol);
        } while (gtk_tree_model_iter_next(sort_model, &it));

        if (!found_selection)
            gtk_tree_selection_select_iter(shell->info_tree->selection, &first);
    }
    return FALSE;
}

static void
check_for_updates(void)
{
    GKeyFile *key_file = g_key_file_new();

    gchar *conf_path = g_build_filename(g_get_user_config_dir(), "hardinfo2",
                                        "settings.ini", NULL);

    g_key_file_load_from_file(
        key_file, conf_path,
        G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);

    gboolean setting = g_key_file_get_boolean(key_file, "Sync", "OnStartup", NULL);
    shell_action_set_active("SyncOnStartupAction", setting);

    g_free(conf_path);
    g_key_file_free(key_file);

    //fetching data -u is used
    if (setting || params.bench_user_note) {
        sync_manager_update_on_startup(0);
    }
}

gboolean hardinfo_link(const gchar *uri) {
    /* Clicked link events pass through here on their
     * way to the default handler (xdg-open).
     *
     * TODO: In the future, links could be used to
     * jump to different pages in hardinfo.
     *
     * if (g_str_has_prefix(uri, "hardinfo2:")) {
     *       hardinfo_navigate(g_utf8_strchr(uri, strlen("hardinfo2"), ':') + 1);
     *       return TRUE;
     * }
     */

    return FALSE; /* didn't handle it */
}

void shell_set_transient_dialog(GtkWindow *dialog)
{
    //DEBUG("TRANSIENT_DIALOG CHANGED!!!!!");
    shell->transient_dialog = dialog ? dialog : GTK_WINDOW(shell->window);
}

void shell_init(GSList * modules)
{
    gchar *path;
    unsigned int latest_ver=0,u1=0,u2=0,u3=0,a1=0,a2=0,a3=0,app_ver=0;
    int fd=-1;

    if (shell) {
	g_error("Shell already created");
	return;
    }

    DEBUG("initializing shell");

    uri_set_function(hardinfo_link);
    params.fmt_opts = FMT_OPT_PANGO;

    create_window();

    //shell_action_set_property("CopyAction", "is-important", TRUE);
    shell_action_set_property("RefreshAction", "is-important", TRUE);
    shell_action_set_property("ReportAction", "is-important", TRUE);
    shell_action_set_property("SyncManagerAction", "is-important", TRUE);
    shell_action_set_property("UpdateAction", "is-important", TRUE);


    path = g_build_filename(g_get_user_config_dir(), "hardinfo2","blobs-update-version.json", NULL);
    fd = open(path,O_RDONLY);
    if(fd>=0){
        char *buf=NULL;
	struct stat st;
	stat(path,&st);
	if(st.st_size>0){
	    buf=g_malloc(st.st_size+1);
	    if(buf){
	        int i=read(fd, buf, st.st_size);
	        if(i>0){
		    buf[st.st_size]=0;
		    char *s=strstr(buf,"\"latest-program-version\"");
	            if(s && sscanf(s,"\"latest-program-version\":\"%u.%u.%u\"",&u1,&u2,&u3)==3) latest_ver=u1*10000+u2*100+u3;
	        }
	    }
        }
        close(fd);
	g_free(buf);
    }
    g_free(path);
    if(sscanf(VERSION,"%u.%u.%u",&a1,&a2,&a3)==3) app_ver=a1*10000+a2*100+a3;
    if(app_ver && (latest_ver > app_ver)){
        shell_action_set_property("UpdateAction", "visible-horizontal", TRUE);
    } else {
        shell_action_set_property("UpdateAction", "visible-horizontal", FALSE);
    }

#if GTK_CHECK_VERSION(3, 0, 0)
    shell_action_set_property("DisableThemeAction", "draw-as-radio", TRUE);
    shell_action_set_property("Theme1Action", "draw-as-radio", TRUE);
    shell_action_set_property("Theme2Action", "draw-as-radio", TRUE);
    shell_action_set_property("Theme3Action", "draw-as-radio", TRUE);
    shell_action_set_property("Theme4Action", "draw-as-radio", TRUE);
    shell_action_set_property("Theme5Action", "draw-as-radio", TRUE);
    shell_action_set_property("Theme6Action", "draw-as-radio", TRUE);
#endif

    shell->tree = tree_new();
    shell->info_tree = info_tree_new();
    shell->loadgraph = load_graph_new(75);
    shell->detail_view = detail_view_new();
    shell_set_transient_dialog(NULL);

    update_tbl = g_hash_table_new_full(g_str_hash, g_str_equal,
                                       g_free, destroy_update_tbl_value);

    gtk_box_pack_start(GTK_BOX(shell->hbox), shell->tree->scroll,
                       FALSE, FALSE, 0);
    gtk_paned_pack1(GTK_PANED(shell->vpaned), shell->info_tree->scroll,
		    SHELL_PACK_RESIZE, SHELL_PACK_SHRINK);

    gtk_notebook_append_page(GTK_NOTEBOOK(shell->notebook),
			     load_graph_get_framed(shell->loadgraph),
			     NULL);
    gtk_notebook_append_page(GTK_NOTEBOOK(shell->notebook),
                             shell->detail_view->scroll, NULL);

    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(shell->notebook), FALSE);
    gtk_notebook_set_show_border(GTK_NOTEBOOK(shell->notebook), FALSE);

    shell->tree->modules = modules;

    check_for_updates();

    g_slist_foreach(shell->tree->modules, shell_add_modules_to_gui, shell->tree);
    gtk_tree_view_expand_all(GTK_TREE_VIEW(shell->tree->view));

    gtk_widget_show_all(shell->hbox);

    load_graph_configure_expose(shell->loadgraph);
    gtk_widget_hide(shell->notebook);
    gtk_widget_hide(shell->note->event_box);

    g_idle_add(select_first_tree_item, NULL);
}

static gboolean update_field(gpointer data)
{
    ShellFieldUpdate *fu;
    struct UpdateTableItem *item;

    fu = (ShellFieldUpdate *)data;
    g_return_val_if_fail(fu != NULL, FALSE);

    item = g_hash_table_lookup(update_tbl, fu->field_name);
    if (!item) {
        return FALSE;
    }

    /* if the entry is still selected, update it */
    if (fu->entry->selected && fu->entry->fieldfunc) {
        gchar *value = fu->entry->fieldfunc(fu->field_name);
        gdouble v;

        if (item->is_iter) {
            /*
             * this function is also used to feed the load graph when ViewType
             * is SHELL_VIEW_LOAD_GRAPH
             */
            if (shell->view_type == SHELL_VIEW_LOAD_GRAPH) {
                GtkTreeIter sort_iter;
                if (gtk_tree_model_sort_convert_child_iter_to_iter(
                        GTK_TREE_MODEL_SORT(shell->info_tree->sort_model),
                        &sort_iter, item->iter)) {
                    if (gtk_tree_selection_iter_is_selected(shell->info_tree->selection,
                                                            &sort_iter)) {
                        load_graph_set_title(shell->loadgraph, fu->field_name);
                        v = atof(value);
                        /* fix KiB->Bytes for UberGraph (GTK3) */
#if GTK_CHECK_VERSION(3, 0, 0)
                        if (strstr(value, "KiB"))
                            v *= 1024;
#endif
                        load_graph_update(shell->loadgraph, v);
                    }
                }
            }

            GtkTreeStore *store = GTK_TREE_STORE(shell->info_tree->model);
            gtk_tree_store_set(store, item->iter, INFO_TREE_COL_VALUE, value, -1);
        } else {
            GList *children = gtk_container_get_children(GTK_CONTAINER(item->widget));
            if (children && children->next->data && value)
                gtk_label_set_markup(GTK_LABEL(children->next->data), value);
            g_list_free(children);
        }

        g_free(value);
        return TRUE;
    }

    if (update_sfusrc) {
        GSList *sfu;

        for (sfu = update_sfusrc; sfu; sfu = sfu->next) {
            g_free(sfu->data);
        }

        g_slist_free(update_sfusrc);
        update_sfusrc = NULL;
    }

    /* otherwise, cleanup and destroy the timeout */
    g_free(fu->field_name);
    g_free(fu);

    return FALSE;
}

#if GTK_CHECK_VERSION(3, 0, 0)
    #define RANGE_SET_VALUE(tree,scrollbar,value) {gtk_range_set_value(GTK_RANGE(gtk_scrolled_window_get_##scrollbar(GTK_SCROLLED_WINDOW(shell->tree->scroll))), value);}
    #define RANGE_GET_VALUE(tree,scrollbar) gtk_range_get_value(GTK_RANGE(gtk_scrolled_window_get_##scrollbar(GTK_SCROLLED_WINDOW(shell->tree->scroll))))
#else
  #define _CONCAT(a,b) a ## b
  #define CONCAT(a,b) _CONCAT(a,b)
  #define RANGE_SET_VALUE(tree, scrollbar, value)			       \
    do {								       \
        GtkRange CONCAT(*range, __LINE__) =                                    \
            GTK_RANGE(GTK_SCROLLED_WINDOW(shell->tree->scroll)->scrollbar);    \
        gtk_range_set_value(CONCAT(range, __LINE__), value);                   \
        gtk_adjustment_value_changed(GTK_ADJUSTMENT(                           \
            gtk_range_get_adjustment(CONCAT(range, __LINE__))));               \
    } while (0)
  #define RANGE_GET_VALUE(tree, scrollbar)                                     \
    gtk_range_get_value(                                                       \
        GTK_RANGE(GTK_SCROLLED_WINDOW(shell->tree->scroll)->scrollbar))
#endif

static void destroy_widget(GtkWidget *widget, gpointer user_data) {
    if(widget) gtk_widget_destroy(widget);
}
static void detail_view_clear(DetailView *detail_view)
{
    gtk_container_forall(GTK_CONTAINER(shell->detail_view->view), destroy_widget, NULL);
}

static gboolean reload_section(gpointer data)
{
    ShellModuleEntry *entry = (ShellModuleEntry *)data;

    if (entry->selected) {
        GtkTreePath *path = NULL;
        GtkTreeIter iter;
        double pos_info_scroll;
        double pos_detail_scroll;

        /* Freeze window updates */
        gdk_window_freeze_updates(gtk_widget_get_window(shell->window));

        /* save current position */
        pos_info_scroll = RANGE_GET_VALUE(info_tree, vscrollbar);
        pos_detail_scroll = RANGE_GET_VALUE(detail_view, vscrollbar);

        /* gets the current selected path from the sort model */
        if (gtk_tree_selection_get_selected(shell->info_tree->selection,
                                            &shell->info_tree->sort_model, &iter)) {
            path = gtk_tree_model_get_path(shell->info_tree->sort_model, &iter);
        }

        /* update the information, clear the treeview and populate it again */
        module_entry_reload(entry);
        module_selected_show_info(entry, TRUE);

        /* if there was a selection, reselect it */
        if (path) {
            gtk_tree_selection_select_path(shell->info_tree->selection, path);
            gtk_tree_view_set_cursor(GTK_TREE_VIEW(shell->info_tree->view),
                                     path, NULL, FALSE);
            gtk_tree_path_free(path);
        }

        /* restore position */
        if (pos_info_scroll) {
            if (strcmp(shell->selected_module->name, _("Network")) == 0) {
                int ii = 5;
                while (ii-- && gtk_events_pending() && !gtk_main_iteration_do(FALSE)) { ; }
            }
            RANGE_SET_VALUE(info_tree, vscrollbar, pos_info_scroll);
        }
        if (pos_detail_scroll)
            RANGE_SET_VALUE(detail_view, vscrollbar, pos_detail_scroll);

        /* UnFreeze widget updates */
        gdk_window_thaw_updates(gtk_widget_get_window(shell->window));
    }

    return FALSE;
}

static gboolean rescan_section(gpointer data)
{
    ShellModuleEntry *entry = (ShellModuleEntry *) data;

    module_entry_reload(entry);

    return entry->selected;
}


static void set_view_type(ShellViewType viewtype, gboolean reload)
{
    gboolean type_changed = FALSE;
    if (viewtype != shell->view_type)
        type_changed = TRUE;

    if (viewtype >= SHELL_VIEW_N_VIEWS)
        viewtype = SHELL_VIEW_NORMAL;

    shell->normalize_percentage = TRUE;
    shell->view_type = viewtype;
    shell->_order_type = SHELL_ORDER_DESCENDING;

    /* reset to the default view columns */
    if (!reload) {
      gtk_tree_view_column_set_visible(shell->info_tree->col_extra1, FALSE);
      gtk_tree_view_column_set_visible(shell->info_tree->col_extra2, FALSE);
      gtk_tree_view_column_set_visible(shell->info_tree->col_progress, FALSE);
      gtk_tree_view_column_set_visible(shell->info_tree->col_value, TRUE);
    }

    /* turn off the rules hint */
#if GTK_CHECK_VERSION(3, 0, 0)
#else
    gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(shell->info_tree->view), FALSE);
#endif

    close_note(NULL, NULL);
    detail_view_clear(shell->detail_view);

    switch (viewtype) {
    default:
    case SHELL_VIEW_NORMAL:
        gtk_widget_show(shell->info_tree->scroll);
        gtk_widget_hide(shell->notebook);

        if (!reload) {
            gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(shell->info_tree->view), FALSE);
        }
        break;
    case SHELL_VIEW_DUAL:
        gtk_widget_show(shell->info_tree->scroll);
        gtk_notebook_set_current_page(GTK_NOTEBOOK(shell->notebook), 1);
        gtk_widget_show(shell->notebook);

        if (type_changed) {
#if GTK_CHECK_VERSION(2, 18, 0)
	    GtkAllocation* alloc = g_new(GtkAllocation, 1);
            gtk_widget_get_allocation(shell->hbox, alloc);
            gtk_paned_set_position(GTK_PANED(shell->vpaned), alloc->height / 2);
            g_free(alloc);
#else
            gtk_paned_set_position(GTK_PANED(shell->vpaned),
                            shell->hbox->allocation.height / 2);
#endif
        }
        break;
    case SHELL_VIEW_LOAD_GRAPH:
        gtk_widget_show(shell->info_tree->scroll);
        gtk_notebook_set_current_page(GTK_NOTEBOOK(shell->notebook), 0);
        gtk_widget_show(shell->notebook);
        load_graph_clear(shell->loadgraph);

        if (type_changed) {
#if GTK_CHECK_VERSION(2, 18, 0)
            GtkAllocation* alloc = g_new(GtkAllocation, 1);
            gtk_widget_get_allocation(shell->hbox, alloc);
            gtk_paned_set_position(GTK_PANED(shell->vpaned),
                    alloc->height - load_graph_get_height(shell->loadgraph) - 16);
            g_free(alloc);
#else
            gtk_paned_set_position(GTK_PANED(shell->vpaned),
                           shell->hbox->allocation.height -
                           load_graph_get_height(shell->loadgraph) - 16);
#endif
        }
        break;
    case SHELL_VIEW_PROGRESS_DUAL:
	gtk_widget_show(shell->notebook);

	gtk_notebook_set_current_page(GTK_NOTEBOOK(shell->notebook), 1);
	/* fallthrough */
    case SHELL_VIEW_PROGRESS:
        gtk_widget_show(shell->info_tree->scroll);

	if (!reload) {
         gtk_tree_view_column_set_visible(shell->info_tree->col_progress, TRUE);
         gtk_tree_view_column_set_visible(shell->info_tree->col_value, FALSE);
        }

	if (viewtype == SHELL_VIEW_PROGRESS)
		gtk_widget_hide(shell->notebook);
	break;
    case SHELL_VIEW_DETAIL:
        gtk_notebook_set_current_page(GTK_NOTEBOOK(shell->notebook), 1);

        gtk_widget_show(shell->notebook);
        gtk_widget_hide(shell->info_tree->scroll);
    }
}

static void group_handle_special(GKeyFile *key_file,
                                 ShellModuleEntry *entry,
                                 const gchar *group,
                                 gchar **keys)
{
    if (!g_str_equal(group, "$ShellParam$")) {
        g_warning("Unknown parameter group: ``%s''", group);
        return;
    }

    gboolean headers_visible = FALSE;
    gint i;

    for (i = 0; keys[i]; i++) {
        gchar *key = keys[i];

        if (g_str_has_prefix(key, "UpdateInterval$")) {
            ShellFieldUpdate *fu = g_new0(ShellFieldUpdate, 1);
            ShellFieldUpdateSource *sfutbl;
            gint ms;

            ms = g_key_file_get_integer(key_file, group, key, NULL);

            /* Old style used just the label which has to be checked by translating it,
             * and potentially could by ambiguous.
             * New style can use tag or label. If new style including a tag,
             * send both tag and label and let the hi_get_field() function use
             * key_get_components() to split it. */
            const gchar *chk = g_utf8_strchr(key, -1, '$');
            fu->field_name = g_strdup(key_is_flagged(chk) ? chk : chk + 1);
            fu->entry = entry;
            sfutbl = g_new0(ShellFieldUpdateSource, 1);
            sfutbl->source_id = g_timeout_add(ms, update_field, fu);
            sfutbl->sfu = fu;

            update_sfusrc = g_slist_prepend(update_sfusrc, sfutbl);
        } else if (g_str_equal(key, "NormalizePercentage")) {
            shell->normalize_percentage =
                g_key_file_get_boolean(key_file, group, key, NULL);
        } else if (g_str_equal(key, "LoadGraphSuffix")) {
            gchar *suffix = g_key_file_get_value(key_file, group, key, NULL);
            load_graph_set_data_suffix(shell->loadgraph, suffix);
            g_free(suffix);
        } else if (g_str_equal(key, "ReloadInterval")) {
            gint ms;

            ms = g_key_file_get_integer(key_file, group, key, NULL);

            g_timeout_add(ms, reload_section, entry);
        } else if (g_str_equal(key, "RescanInterval")) {
            gint ms;

            ms = g_key_file_get_integer(key_file, group, key, NULL);

            g_timeout_add(ms, rescan_section, entry);
        } else if (g_str_equal(key, "ShowColumnHeaders")) {
            headers_visible =
                g_key_file_get_boolean(key_file, group, key, NULL);
        } else if (g_str_has_prefix(key, "ColumnTitle")) {
            GtkTreeViewColumn *column = NULL;
            gchar *value, *title = g_utf8_strchr(key, -1, '$') + 1;

            value = g_key_file_get_value(key_file, group, key, NULL);

            if (g_str_equal(title, "Extra1")) {
                column = shell->info_tree->col_extra1;
            } else if (g_str_equal(title, "Extra2")) {
                column = shell->info_tree->col_extra2;
            } else if (g_str_equal(title, "Value")) {
                column = shell->info_tree->col_value;
            } else if (g_str_equal(title, "TextValue")) {
                column = shell->info_tree->col_textvalue;
            } else if (g_str_equal(title, "Progress")) {
                column = shell->info_tree->col_progress;
            }

            if (column) {
                gtk_tree_view_column_set_title(column, value);
                gtk_tree_view_column_set_visible(column, TRUE);
            }

            g_free(value);
        } else if (g_str_equal(key, "OrderType")) {
            shell->_order_type =
                g_key_file_get_integer(key_file, group, key, NULL);
        } else if (g_str_has_prefix(key, "NaturalSort$")) {
            gchar *col_name = g_utf8_strchr(key, -1, '$') + 1;

            if (g_str_equal(col_name, "TextValue"))
                shell->info_tree->natural_sort_columns |= (1 << INFO_TREE_COL_NAME);
            else if (g_str_equal(col_name, "Value"))
                shell->info_tree->natural_sort_columns |= (1 << INFO_TREE_COL_VALUE);
            else if (g_str_equal(col_name, "Extra1"))
                shell->info_tree->natural_sort_columns |= (1 << INFO_TREE_COL_EXTRA1);
            else if (g_str_equal(col_name, "Extra2"))
                shell->info_tree->natural_sort_columns |= (1 << INFO_TREE_COL_EXTRA2);
            /* natural sort on progress view is unavailable */
        } else if (g_str_has_prefix(key, "NoSort$")) {
            gchar *col_name = g_utf8_strchr(key, -1, '$') + 1;

            if (g_str_equal(col_name, "TextValue"))
                shell->info_tree->nosort_columns |= (1 << INFO_TREE_COL_NAME);
            else if (g_str_equal(col_name, "Value"))
                shell->info_tree->nosort_columns |= (1 << INFO_TREE_COL_VALUE);
            else if (g_str_equal(col_name, "Extra1"))
                shell->info_tree->nosort_columns |= (1 << INFO_TREE_COL_EXTRA1);
            else if (g_str_equal(col_name, "Extra2"))
                shell->info_tree->nosort_columns |= (1 << INFO_TREE_COL_EXTRA2);
            else if (g_str_equal(col_name, "Progress"))
                shell->info_tree->nosort_columns |= (1 << INFO_TREE_COL_PROGRESS);
        } else if (g_str_has_prefix(key, "DefaultSort$")) {
            const gchar *col_name = g_utf8_strchr(key, -1, '$') + 1;
            gchar *value = g_key_file_get_value(key_file, group, key, NULL);
            gint cid = -1;

            if (g_str_equal(col_name, "TextValue"))
                cid = INFO_TREE_COL_NAME;
            else if (g_str_equal(col_name, "Value"))
                cid = INFO_TREE_COL_VALUE;
            else if (g_str_equal(col_name, "Extra1"))
                cid = INFO_TREE_COL_EXTRA1;
            else if (g_str_equal(col_name, "Extra2"))
                cid = INFO_TREE_COL_EXTRA2;
            else if (g_str_equal(col_name, "Progress"))
                cid = INFO_TREE_COL_PROGRESS;
            if (cid > -1 && value) {
                shell->info_tree->default_sort_column = cid;
                shell->info_tree->default_sort_order =
                    (g_str_equal(value, "desc") || g_str_equal(value, "descending"))
                    ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
            }
            g_free(value);
        } else if (g_str_has_prefix(key, "DateSort$")) {
            gchar *col_name = g_utf8_strchr(key, -1, '$') + 1;

            /* only support text value view */
            if (g_str_equal(col_name, "TextValue"))
                shell->info_tree->date_sort_columns |= (1 << INFO_TREE_COL_NAME);
        } else if (g_str_has_prefix(key, "Icon$")) {
            struct UpdateTableItem *item;

            const gchar *ikey = g_utf8_strchr(key, -1, '$');
            gchar *tag=NULL, *name=NULL;
            key_get_components(ikey, NULL, &tag, &name, NULL, NULL);

            if (tag)
                item = g_hash_table_lookup(update_tbl, tag);
             else
                item = g_hash_table_lookup(update_tbl, name);
            g_free(name);
            g_free(tag);

            if (item) {
                gchar *file = g_key_file_get_value(key_file, group, key, NULL);
		GdkPixbuf *pixbuf;
		if(strstr(file,"LARGE")==file){
		    file=strreplace(file,"LARGE","");
                     pixbuf = icon_cache_get_pixbuf_at_size(file, -1, 33);
		} else {
                     pixbuf = icon_cache_get_pixbuf_at_size(file, 22, 22);
		}

                g_free(file);

                if (item->is_iter) {
                    gtk_tree_store_set(
                        GTK_TREE_STORE(shell->info_tree->model), item->iter,
                        INFO_TREE_COL_PBUF, pixbuf, -1);
                } else {
                    GList *children = gtk_container_get_children(GTK_CONTAINER(item->widget));
                    gtk_image_set_from_pixbuf(GTK_IMAGE(children->data), pixbuf);
                    gtk_widget_show(GTK_WIDGET(children->data));
                    g_list_free(children);
                }
            }
        } else if (g_str_equal(key, "Zebra")) {
#if GTK_CHECK_VERSION(3, 0, 0)
#else
            gtk_tree_view_set_rules_hint(
                GTK_TREE_VIEW(shell->info_tree->view),
                g_key_file_get_boolean(key_file, group, key, NULL));
#endif
        }
    }

    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(shell->info_tree->view),
                                      headers_visible);
}

static void group_handle_normal(GKeyFile *key_file,
                                ShellModuleEntry *entry,
                                const gchar *group,
                                gchar **keys,
                                gsize ngroups)
{
    GtkTreeIter parent;
    GtkTreeStore *store = GTK_TREE_STORE(shell->info_tree->model);
    gint i;

    if (ngroups > 1) {
        gtk_tree_store_append(store, &parent, NULL);

        gchar *tmp = g_strdup(group);
        strend(tmp, '#');
        gtk_tree_store_set(store, &parent, INFO_TREE_COL_NAME, tmp, -1);
        g_free(tmp);
    }

    g_key_file_set_list_separator(key_file, '|');

    for (i = 0; keys[i]; i++) {
        gchar *key = keys[i];
        gchar **values;
        gsize vcount = 0;
        GtkTreeIter child;

        values = g_key_file_get_string_list(key_file, group, key, &vcount, NULL);
        if (!vcount) {
            /* Check for empty value */
            values = g_new0(gchar*, 2);
            values[0] = g_key_file_get_string(key_file, group, key, NULL);
            if (values[0]) {
                vcount = 1;
            } else {
                g_strfreev(values);
                continue;
            }
        }

        if (entry->fieldfunc && values[0] && g_str_equal(values[0], "...")) {
            g_free(values[0]);
            values[0] = entry->fieldfunc(key);
        }

        if (ngroups == 1) {
            gtk_tree_store_append(store, &child, NULL);
        } else {
            gtk_tree_store_append(store, &child, &parent);
        }

        if (vcount > 0)
            gtk_tree_store_set(store, &child, INFO_TREE_COL_VALUE,
                               values[0], -1);
        if (vcount > 1)
            gtk_tree_store_set(store, &child, INFO_TREE_COL_EXTRA1,
                               values[1], -1);
        if (vcount > 2)
            gtk_tree_store_set(store, &child, INFO_TREE_COL_EXTRA2,
                               values[2], -1);

        struct UpdateTableItem *item = g_new0(struct UpdateTableItem, 1);
        item->is_iter = TRUE;
        item->iter = gtk_tree_iter_copy(&child);
        gchar *flags=NULL, *tag=NULL, *name=NULL, *label=NULL;
        key_get_components(key, &flags, &tag, &name, &label, NULL);

        if (flags) {
            //TODO: name was formerly used where label is here. Check all uses
            //for problems.
            gtk_tree_store_set(store, &child, INFO_TREE_COL_NAME, label,
                               INFO_TREE_COL_DATA, flags, -1);
            g_hash_table_insert(update_tbl, tag, item);
	    g_free(name);
        } else {
            gtk_tree_store_set(store, &child, INFO_TREE_COL_NAME, key,
                               INFO_TREE_COL_DATA, NULL, -1);
            g_hash_table_insert(update_tbl, name, item);
	    g_free(tag);
        }
        //g_free(tag);
        //g_free(name);
        g_free(flags);
        g_free(label);

        g_strfreev(values);
    }
}

static void update_progress()
{
    GtkTreeModel *model = shell->info_tree->model;
    GtkTreeStore *store = GTK_TREE_STORE(model);
    GtkTreeIter iter, fiter;
    gchar *tmp;
    gdouble maxv = INT_MIN, minv = INT_MAX, coeff, cur;

    if (!gtk_tree_model_get_iter_first(model, &fiter))
	return;

    /* finds the maximum value */
    if (shell->normalize_percentage) {
	iter = fiter;
	do {
	    gtk_tree_model_get(model, &iter, INFO_TREE_COL_VALUE, &tmp, -1);

	    cur = atof(tmp);
	    if (cur > maxv)
		maxv = cur;
	    if (cur < minv)
		minv = cur;

	    g_free(tmp);
	} while (gtk_tree_model_iter_next(model, &iter));

	if (minv - maxv < 0.001)
	    maxv += 1.0f;
    } else {
	minv = 1.0f;
	maxv = 100.0f;
    }

    coeff = (100.0f - 1.0f) / (maxv - minv);

    /* fix the maximum relative percentage */
    iter = fiter;
    do {
	char *space;
	char formatted[128];
	gdouble pct;

	gtk_tree_model_get(model, &iter, INFO_TREE_COL_VALUE, &tmp, -1);
	cur = atof(tmp);
	space = g_utf8_strchr(tmp, -1, ' ');

	pct = coeff * (cur - minv) + 1.0f;
	if (shell->_order_type == SHELL_ORDER_ASCENDING)
	    pct = 100.0 - pct;
	pct = ceil(pct);

	if (space) {
	    snprintf(formatted, sizeof(formatted), "%.2f%s", cur, space);
	} else {
	    snprintf(formatted, sizeof(formatted), "%.2f", cur);
	}

	gtk_tree_store_set(store, &iter, INFO_TREE_COL_PROGRESS, pct,
		           INFO_TREE_COL_VALUE,
			   strreplace_chr(formatted, ',', '.'), -1);

	g_free(tmp);
    } while (gtk_tree_model_iter_next(model, &iter));

}

void shell_set_note_from_entry(ShellModuleEntry * entry)
{
    if (entry->notefunc) {
        const gchar *note = module_entry_get_note(entry);

	if (note) {
	    gtk_label_set_markup(GTK_LABEL(shell->note->label), note);
	    gtk_widget_show(shell->note->event_box);
	} else {
	    gtk_widget_hide(shell->note->event_box);
	}
    } else {
	gtk_widget_hide(shell->note->event_box);
    }
}

void shell_clear_field_updates(void)
{
    if (update_sfusrc) {
        GSList *sfusrc;

        for (sfusrc = update_sfusrc; sfusrc; sfusrc = sfusrc->next) {
            ShellFieldUpdateSource *src =
                (ShellFieldUpdateSource *) sfusrc->data;
            g_source_remove(src->source_id);
            g_free(src->sfu->field_name);
            g_free(src->sfu);
            g_free(src);
        }

        g_slist_free(update_sfusrc);
        update_sfusrc = NULL;
    }
}


static void module_selected_show_info_list(GKeyFile *key_file,
                                           ShellModuleEntry *entry,
                                           gchar **groups,
                                           gsize ngroups,
                                           gboolean reload)
{
#if GTK_CHECK_VERSION(3, 0, 0)
    GtkCssProvider *provider;
    provider = gtk_css_provider_new();
#endif
    GtkTreeStore *store = GTK_TREE_STORE(shell->info_tree->model);
    GtkTreeSortable *sortable = GTK_TREE_SORTABLE(shell->info_tree->sort_model);
    /* to set natrual sort and date sort, ignore progress col */
    static const gint col_ids[] = { INFO_TREE_COL_NAME, INFO_TREE_COL_VALUE,
                                    INFO_TREE_COL_EXTRA1, INFO_TREE_COL_EXTRA2 };
    gint i;
    guint x;

    shell->info_tree->natural_sort_columns = 0;
    shell->info_tree->nosort_columns = 0;
    shell->info_tree->default_sort_column = -1;
    shell->info_tree->date_sort_columns = 0;

    gtk_tree_store_clear(store);

    g_object_ref(shell->info_tree->sort_model);
    gtk_tree_view_set_model(GTK_TREE_VIEW(shell->info_tree->view), NULL);

    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(shell->info_tree->view), FALSE);

#if GTK_CHECK_VERSION(3, 0, 0)
    if(params.theme>0){
      if(params.darkmode){
        gtk_css_provider_load_from_data(provider, "treeview { background-color: rgba(0xa0, 0xa0, 0xa0, 0.1); } treeview:selected { background-color: rgba(0x40, 0x60, 0xff, 1); } ", -1, NULL);
        gtk_style_context_add_provider(gtk_widget_get_style_context(shell->info_tree->view), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
      }else{
        gtk_css_provider_load_from_data(provider, "treeview { background-color: rgba(0x60, 0x60, 0x60, 0.1); } treeview:selected { background-color: rgba(0x40, 0x60, 0xff, 1); } ", -1, NULL);
        gtk_style_context_add_provider(gtk_widget_get_style_context(shell->info_tree->view), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
      }
    }
#endif

    for (i = 0; groups[i]; i++) {
        gchar **keys = g_key_file_get_keys(key_file, groups[i], NULL, NULL);

        if (groups[i][0] == '$') {
            group_handle_special(key_file, entry, groups[i], keys);
        } else {
            group_handle_normal(key_file, entry, groups[i], keys, ngroups);
        }

        g_strfreev(keys);
    }

    g_object_unref(shell->info_tree->sort_model);
    gtk_tree_view_set_model(GTK_TREE_VIEW(shell->info_tree->view), shell->info_tree->sort_model);

    gtk_tree_view_column_set_clickable(shell->info_tree->col_textvalue, TRUE);
    gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_textvalue, INFO_TREE_COL_NAME);
    gtk_tree_view_column_set_clickable(shell->info_tree->col_value, TRUE);
    gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_value, INFO_TREE_COL_VALUE);
    gtk_tree_view_column_set_clickable(shell->info_tree->col_extra1, TRUE);
    gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_extra1, INFO_TREE_COL_EXTRA1);
    gtk_tree_view_column_set_clickable(shell->info_tree->col_extra2, TRUE);
    gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_extra2, INFO_TREE_COL_EXTRA2);
    gtk_tree_view_column_set_clickable(shell->info_tree->col_progress, TRUE);
    gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_progress, INFO_TREE_COL_PROGRESS);

    /* disable sort here */
    if (shell->info_tree->nosort_columns & (1 << INFO_TREE_COL_NAME)) {
        gtk_tree_view_column_set_clickable(shell->info_tree->col_textvalue, FALSE);
        gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_textvalue, -1);
    }
    if (shell->info_tree->nosort_columns & (1 << INFO_TREE_COL_VALUE)) {
        gtk_tree_view_column_set_clickable(shell->info_tree->col_value, FALSE);
        gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_value, -1);
    }
    if (shell->info_tree->nosort_columns & (1 << INFO_TREE_COL_EXTRA1)) {
        gtk_tree_view_column_set_clickable(shell->info_tree->col_extra1, FALSE);
        gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_extra1, -1);
    }
    if (shell->info_tree->nosort_columns & (1 << INFO_TREE_COL_EXTRA2)) {
        gtk_tree_view_column_set_clickable(shell->info_tree->col_extra2, FALSE);
        gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_extra2, -1);
    }
    if (shell->info_tree->nosort_columns & (1 << INFO_TREE_COL_PROGRESS)) {
        gtk_tree_view_column_set_clickable(shell->info_tree->col_progress, FALSE);
        gtk_tree_view_column_set_sort_column_id(shell->info_tree->col_progress, -1);
    }

    for (x = 0; x < G_N_ELEMENTS(col_ids); x++) {
        GtkTreeIterCompareFunc func = info_tree_default_strcmp;

        if ((shell->info_tree->date_sort_columns & (1 << col_ids[x]))
              /* date sort only for name col */
              && col_ids[x] == INFO_TREE_COL_NAME) {
            func = info_tree_date_compare;
        } else if (shell->info_tree->natural_sort_columns & (1 << col_ids[x])) {
            func = info_tree_natural_compare;
        }

        gtk_tree_sortable_set_sort_func(sortable, col_ids[x], func,
                                        GINT_TO_POINTER(col_ids[x]), NULL);
    }

    if (!reload) {
        if (shell->info_tree->default_sort_column > -1) {
            gtk_tree_sortable_set_sort_column_id(
                GTK_TREE_SORTABLE(shell->info_tree->sort_model),
                                  shell->info_tree->default_sort_column,
                                  shell->info_tree->default_sort_order);
        } else {
            if (shell->view_type == SHELL_VIEW_PROGRESS || shell->view_type == SHELL_VIEW_PROGRESS_DUAL) {
                gtk_tree_sortable_set_sort_column_id(
                    GTK_TREE_SORTABLE(shell->info_tree->sort_model),
                                      INFO_TREE_COL_PROGRESS,
                                      GTK_SORT_DESCENDING);
            } else if (shell->view_type != SHELL_VIEW_NORMAL) {
                gtk_tree_sortable_set_sort_column_id(
                    GTK_TREE_SORTABLE(shell->info_tree->sort_model),
                                      INFO_TREE_COL_NAME,
                                      GTK_SORT_ASCENDING);
            }
        }
    }

    gtk_tree_view_expand_all(GTK_TREE_VIEW(shell->info_tree->view));
    gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(shell->info_tree->view), ngroups > 1);
}

static gboolean detail_activate_link (GtkLabel *label, gchar *uri, gpointer user_data) {
    if (g_str_has_prefix(uri, "http://") || g_str_has_prefix(uri, "https://")) {
         return uri_open(uri);
    }
    return false;
}

static gchar *vendor_info_markup(const Vendor *v) {
    if (!v) return NULL;
    gchar *ven_mt = NULL;
    gchar *full_link = NULL, *p = NULL;
    gchar *ven_tag = v->name_short ? g_strdup(v->name_short) : g_strdup(v->name);
    tag_vendor(&ven_tag, 0, ven_tag, v->ansi_color, FMT_OPT_PANGO);
    //if (v->name_short)
    //    ven_mt = appf(ven_mt, "\n", "%s", v->name);
    ven_mt = appf(ven_mt, "\n", "%s", ven_tag);
    if (v->url) {
        if (!g_str_has_prefix(v->url, "http") )
            full_link = g_strdup_printf("http://%s", v->url);
        ven_mt = appf(ven_mt, "\n", "<b>%s:</b> <a href=\"%s\">%s</a>", _("URL"), full_link ? full_link : v->url, v->url);
        g_free(full_link);
        full_link = NULL;
    }
    if (v->url_support) {
        if (!g_str_has_prefix(v->url_support, "http") )
            full_link = g_strdup_printf("http://%s", v->url_support);
        ven_mt = appf(ven_mt, "\n", "<b>%s:</b> <a href=\"%s\">%s</a>", _("Support URL"), full_link ? full_link : v->url_support, v->url_support);
        g_free(full_link);
        full_link = NULL;
    }
    if (v->wikipedia) {
        /* sending the title to wikipedia.com/wiki will autmatically handle the language and section parts,
         * but perhaps that shouldn't be relied on so much? */
        full_link = g_strdup_printf("http://wikipedia.com/wiki/%s", v->wikipedia);
        for(p = full_link; *p; p++) {
            if (*p == ' ') *p = '_';
        }
        ven_mt = appf(ven_mt, "\n", "<b>%s:</b> <a href=\"%s\">%s</a>", _("Wikipedia"), full_link ? full_link : v->wikipedia, v->wikipedia);
        g_free(full_link);
        full_link = NULL;
    }
    g_free(ven_tag);
    return ven_mt;
}

static void module_selected_show_info_detail(GKeyFile *key_file,
                                             ShellModuleEntry *entry,
                                             gchar **groups)
{
    gint i;

    detail_view_clear(shell->detail_view);

    for (i = 0; groups[i]; i++) {
        gsize nkeys;
        gchar **keys = g_key_file_get_keys(key_file, groups[i], &nkeys, NULL);
        gchar *group_label = g_strdup(groups[i]);
        strend(group_label, '#');

        if (entry && groups[i][0] == '$') {
            group_handle_special(key_file, entry, groups[i], keys);
        } else {
            gchar *tmp = g_strdup_printf("<b>%s</b>", group_label);
            GtkWidget *label = gtk_label_new(tmp);
            gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
            GtkWidget *frame = gtk_frame_new(NULL);
            gtk_frame_set_label_widget(GTK_FRAME(frame), label);
            gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
            g_free(tmp);

            gtk_container_set_border_width(GTK_CONTAINER(frame), 6);
            gtk_box_pack_start(GTK_BOX(shell->detail_view->view), frame, FALSE,
                               FALSE, 0);

            GtkWidget *table = gtk_table_new(nkeys, 2, FALSE);
            gtk_container_set_border_width(GTK_CONTAINER(table), 4);
            gtk_container_add(GTK_CONTAINER(frame), table);

            gint j, a = 0;
            for (j = 0; keys[j]; j++) {
                gchar *key_markup;
                gchar *value;
                gchar *name=NULL, *lbl=NULL, *tag=NULL, *flags=NULL;
                key_get_components(keys[j], &flags, &tag, &name, &lbl, NULL);

                value = g_key_file_get_string(key_file, groups[i], keys[j], NULL);

                if (entry && entry->fieldfunc && value && g_str_equal(value, "...")) {
                    g_free(value);
                    value = entry->fieldfunc(keys[j]);
                }

                gboolean has_ven = key_value_has_vendor_string(flags);
                const Vendor *v = has_ven ? vendor_match(value, NULL) : NULL;

                if(params.darkmode){
                    key_markup = g_strdup_printf("<span color=\"#8af\">%s</span>", lbl);
		} else {
                    key_markup = g_strdup_printf("<span color=\"#24f\">%s</span>", lbl);
		}

                GtkWidget *key_label = gtk_label_new(key_markup);
                gtk_label_set_use_markup(GTK_LABEL(key_label), TRUE);
                gtk_misc_set_alignment(GTK_MISC(key_label), 1.0f, 0.5f);

                GtkWidget *value_label = gtk_label_new(value);
                gtk_label_set_use_markup(GTK_LABEL(value_label), TRUE);
                gtk_label_set_selectable(GTK_LABEL(value_label), TRUE);
#if !GTK_CHECK_VERSION(3, 0, 0)
                gtk_label_set_line_wrap(GTK_LABEL(value_label), TRUE);
#endif
                gtk_misc_set_alignment(GTK_MISC(value_label), 0.0f, 0.5f);

                GtkWidget *value_icon = gtk_image_new();

                GtkWidget *value_box = gtk_hbox_new(FALSE, 4);
                gtk_box_pack_start(GTK_BOX(value_box), value_icon, FALSE, FALSE, 0);
                gtk_box_pack_start(GTK_BOX(value_box), value_label, TRUE, TRUE, 0);

                g_signal_connect(key_label, "activate-link", G_CALLBACK(detail_activate_link), NULL);
                g_signal_connect(value_label, "activate-link", G_CALLBACK(detail_activate_link), NULL);

                gtk_widget_show(key_label);
                gtk_widget_show(value_box);
                gtk_widget_show(value_label);

                gtk_table_attach(GTK_TABLE(table), key_label, 0, 1, j + a, j + a + 1,
                                 GTK_FILL, GTK_FILL, 6, 4);
                gtk_table_attach(GTK_TABLE(table), value_box, 1, 2, j + a, j + a + 1,
                                 GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 4);

                if (v) {
                    a++; /* insert a row */
                    gchar *vendor_markup = vendor_info_markup(v);
                    GtkWidget *vendor_label = gtk_label_new(vendor_markup);
                    gtk_label_set_use_markup(GTK_LABEL(vendor_label), TRUE);
                    gtk_label_set_selectable(GTK_LABEL(vendor_label), TRUE);
                    gtk_misc_set_alignment(GTK_MISC(vendor_label), 0.0f, 0.5f);
                    g_signal_connect(vendor_label, "activate-link", G_CALLBACK(detail_activate_link), NULL);
                    GtkWidget *vendor_box = gtk_hbox_new(FALSE, 4);
                    gtk_box_pack_start(GTK_BOX(vendor_box), vendor_label, TRUE, TRUE, 0);
                    gtk_table_attach(GTK_TABLE(table), vendor_box, 1, 2, j + a, j + a + 1,
                                     GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 4);
                    gtk_widget_show(vendor_box);
                    gtk_widget_show(vendor_label);
                    g_free(vendor_markup);
                }

                struct UpdateTableItem *item = g_new0(struct UpdateTableItem, 1);
                item->is_iter = FALSE;
                item->widget = g_object_ref(value_box);

                if (tag) {
                    g_hash_table_insert(update_tbl, tag, item);
		    g_free(name);
                } else {
                    g_hash_table_insert(update_tbl, name, item);
		    g_free(tag);
                }

                //g_free(tag);
                //g_free(name);
                g_free(flags);
                g_free(lbl);
                g_free(value);
                g_free(key_markup);
            }

            gtk_widget_show(table);
            gtk_widget_show(label);
            gtk_widget_show(frame);
        }

        g_strfreev(keys);
        g_free(group_label);

        /* disable right-click context menus for all new widgets  */
        disable_context_menus(GTK_WIDGET(shell->detail_view->view));
    }
}

static void
module_selected_show_info(ShellModuleEntry *entry, gboolean reload)
{
    gsize ngroups;
    gint i;

    module_entry_scan(entry);
    shell_clear_field_updates();
    if (!reload) {
        /* recreate the iter hash table */
        h_hash_table_remove_all(update_tbl);
    }

    GKeyFile *key_file = g_key_file_new();
    gchar *key_data = module_entry_function(entry);

    g_key_file_load_from_data(key_file, key_data, strlen(key_data), 0, NULL);
    set_view_type(g_key_file_get_integer(key_file, "$ShellParam$",
                                         "ViewType", NULL), reload);

    gchar **groups = g_key_file_get_groups(key_file, &ngroups);

    for (i = 0; groups[i]; i++) {
	if (groups[i][0] == '$')
	    ngroups--;
    }

    if (shell->view_type == SHELL_VIEW_DETAIL) {
        module_selected_show_info_detail(key_file, entry, groups);
    } else {
        module_selected_show_info_list(key_file, entry, groups, ngroups, reload);
    }

    g_strfreev(groups);
    g_key_file_free(key_file);
    g_free(key_data);

    switch (shell->view_type) {
    case SHELL_VIEW_PROGRESS_DUAL:
    case SHELL_VIEW_PROGRESS:
        update_progress();
        break;
    default:
        break;
    }

    shell_set_note_from_entry(entry);

    update_search_widgets_state();

    //Scroll to selected item or first
    if(entry->flags & MODULE_FLAG_BENCHMARK){
	select_marked_or_first_item(NULL);
        g_idle_add(select_marked_or_first_item, NULL);
        g_idle_add(select_marked_or_first_item, NULL);
    } else {
        if((shell->view_type==SHELL_VIEW_DUAL)||(shell->view_type==SHELL_VIEW_LOAD_GRAPH)||(shell->view_type==SHELL_VIEW_PROGRESS_DUAL)){
	    select_marked_or_first_item(NULL);
	}
    }
}

static void info_selected_show_extra(const gchar *tag)
{
    if (!tag || !shell->selected->morefunc)
        return;

    GKeyFile *key_file = g_key_file_new();
    gchar *key_data = shell->selected->morefunc((gchar *)tag);
    gchar **groups;

    g_key_file_load_from_data(key_file, key_data, strlen(key_data), 0, NULL);
    groups = g_key_file_get_groups(key_file, NULL);

    module_selected_show_info_detail(key_file, NULL, groups);

    g_strfreev(groups);
    g_key_file_free(key_file);
    g_free(key_data);
}

static gchar *detail_view_clear_value(gchar *value)
{
     GKeyFile *keyfile;
     gchar *return_value;

     keyfile = g_key_file_new();
     if (!value) return_value = g_strdup("");
     else
     if (g_key_file_load_from_data(keyfile, value,
                                   strlen(value), 0, NULL)) {
          gchar **groups;
          gint group;

          return_value = g_strdup("");

          groups = g_key_file_get_groups(keyfile, NULL);
          for (group = 0; groups[group]; group++) {
               gchar **keys;
               gint key;

               keys = g_key_file_get_keys(keyfile, groups[group], NULL, NULL);
               for (key = 0; keys[key]; key++) {
                  gchar *temp = keys[key];

                  if (*temp == '$') {
                     temp++;
                     while (*temp && *temp != '$')
                        temp++;
                     temp++;

                     return_value = h_strdup_cprintf("%s\n", return_value, temp);
                  } else {
                     g_free(return_value);
                     return_value = g_key_file_get_string(keyfile, groups[group],
                                                          keys[key], NULL);
                  }
               }

               g_strfreev(keys);
          }

          g_strfreev(groups);
     } else {
          return_value = g_strdup(value);
     }

     g_key_file_free(keyfile);

     return g_strstrip(return_value);
}

static void detail_view_add_item(DetailView *detail_view,
                                 gchar *icon,
                                 gchar *name,
                                 gchar *value)
{
#if GTK_CHECK_VERSION(3, 0, 0)
#else
    GtkWidget *alignment;
#endif
    GtkWidget *frame;
    GtkWidget *frame_label_box;
    GtkWidget *frame_image;
    GtkWidget *frame_label;
    GtkWidget *content;
    gchar *temp;

    temp = detail_view_clear_value(value);

    /* creates the frame */
    frame = gtk_frame_new(NULL);
    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);

#if GTK_CHECK_VERSION(3, 0, 0)
    frame_label_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
#else
    frame_label_box = gtk_hbox_new(FALSE, 5);
#endif
    frame_image = icon_cache_get_image(icon);
    frame_label = gtk_label_new(name);
    gtk_label_set_use_markup(GTK_LABEL(frame_label), TRUE);
    gtk_box_pack_start(GTK_BOX(frame_label_box), frame_image, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(frame_label_box), frame_label, FALSE, FALSE, 0);

    content = gtk_label_new(temp);
#if GTK_CHECK_VERSION(3, 0, 0)
    GtkWidget *frame_box;
    frame_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
    gtk_widget_set_margin_start(GTK_WIDGET(frame_box), 48);
    gtk_box_pack_start(GTK_BOX(frame_box), content, FALSE, FALSE, 0);
    gtk_container_add(GTK_CONTAINER(frame), frame_box);
#else
    alignment = gtk_alignment_new(0.5, 0.5, 1, 1);
    gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0, 48, 0);
    gtk_widget_show(alignment);
    gtk_container_add(GTK_CONTAINER(frame), alignment);
    gtk_misc_set_alignment(GTK_MISC(content), 0.0, 0.5);
    gtk_container_add(GTK_CONTAINER(alignment), content);
#endif

    gtk_widget_show_all(frame);
    gtk_widget_show_all(frame_label_box);

    gtk_frame_set_label_widget(GTK_FRAME(frame), frame_label_box);

    /* pack the item on the detail_view screen */
    gtk_box_pack_start(GTK_BOX(shell->detail_view->view), frame, FALSE, FALSE, 4);

    g_free(temp);
}

static void detail_view_create_header(DetailView *detail_view,
                                        gchar *title)
{
    GtkWidget *header, *label;
    gchar *temp;

    temp = g_strdup_printf(_("<b>%s \342\206\222 Summary</b>"), title);

    header = gtk_menu_item_new_with_label(temp);
    gtk_menu_item_select(GTK_MENU_ITEM(header));
    gtk_widget_show(header);

    label = gtk_bin_get_child(GTK_BIN(header));
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);

    gtk_box_pack_start(GTK_BOX(shell->detail_view->view), header, FALSE, FALSE, 4);

    g_free(temp);
}

static void shell_show_detail_view(void)
{
    GKeyFile *keyfile;
    gchar *detail;

    set_view_type(SHELL_VIEW_DETAIL, FALSE);
    detail_view_clear(shell->detail_view);
    detail_view_create_header(shell->detail_view, shell->selected_module->name);

    keyfile = g_key_file_new();
    detail = shell->selected_module->summaryfunc();

    if (g_key_file_load_from_data(keyfile, detail,
                                  strlen(detail), 0, NULL)) {
         gchar **groups;
         gint group;

         groups = g_key_file_get_groups(keyfile, NULL);

         for (group = 0; groups[group]; group++) {
             gchar *icon=NULL, *method=NULL, *method_result=NULL;

             shell_status_pulse();

             icon = g_key_file_get_string(keyfile, groups[group], "Icon", NULL);
             method = g_key_file_get_string(keyfile, groups[group], "Method", NULL);
             if (method) {
                  method_result = module_call_method(method);
             } else {
                  method_result = g_strdup("N/A");
             }

             detail_view_add_item(shell->detail_view,
                                    icon, groups[group], method_result);
             shell_status_pulse();

             g_free(icon);
             g_free(method);
             g_free(method_result);
         }

         g_strfreev(groups);
    } else {
         DEBUG("error while parsing detail_view");
         set_view_type(SHELL_VIEW_NORMAL, FALSE);
    }

    g_free(detail);
    g_key_file_free(keyfile);

    /* disable right-click context menus for all new widgets  */
    disable_context_menus(GTK_WIDGET(shell->detail_view->view));
    shell_view_set_enabled(TRUE);
}

static void module_selected(gpointer data)
{
    ShellTree *shelltree = shell->tree;
    GtkTreeModel *model = GTK_TREE_MODEL(shelltree->model);
    GtkTreeIter iter, parent;
    ShellModuleEntry *entry;
    static ShellModuleEntry *current = NULL;
    static gchar *current_module_name = NULL;
    static gboolean updating = FALSE;

    /* Gets the currently selected item on the left-side TreeView; if there is
       no selection, silently return */
    if (!gtk_tree_selection_get_selected(shelltree->selection, &model, &iter)) {
        return;
    }

    /* Mark the currently selected module as "unselected"; this is used to kill
       the update timeout. */
    if (current) {
        current->selected = FALSE;
    }

    if (updating) {
        return;
    } else {
        updating = TRUE;
    }

    if (!gtk_tree_model_iter_parent(model, &parent, &iter)) {
        memcpy(&parent, &iter, sizeof(iter));
    }

    gtk_tree_model_get(model, &parent, TREE_COL_MODULE, &shell->selected_module, -1);

    gboolean module_changed = FALSE;

    if (shell->selected_module && shell->selected_module->name) {
        if (!current_module_name) {
            /* assume module changed at first time */
            module_changed = TRUE;
            current_module_name = g_strdup(shell->selected_module->name);
        } else if (strcmp(current_module_name, shell->selected_module->name) != 0) {
            module_changed = TRUE;
            g_free(current_module_name);
            current_module_name = g_strdup(shell->selected_module->name);
        }
    }

    /* Get the current selection and shows its related info */
    gtk_tree_model_get(model, &iter, TREE_COL_MODULE_ENTRY, &entry, -1);
    if (entry && !entry->selected) {
        gchar *title=NULL;

        shell_status_set_enabled(TRUE);
        shell_status_update(_("Updating..."));
        entry->selected = TRUE;
        shell->selected = entry;
        module_selected_show_info(entry, FALSE);

        gtk_tree_view_columns_autosize(GTK_TREE_VIEW(shell->info_tree->view));

	if(entry->flags & MODULE_FLAG_BENCHMARK) {
	    //allow to scroll to selected THIS-Machine
	} else {
            RANGE_SET_VALUE(info_tree, vscrollbar, 0.0);
	}
        RANGE_SET_VALUE(info_tree, hscrollbar, 0.0);

        if (shell->view_type == SHELL_VIEW_DETAIL || shell->view_type == SHELL_VIEW_DUAL) {
            RANGE_SET_VALUE(detail_view, vscrollbar, 0.0);
            RANGE_SET_VALUE(detail_view, hscrollbar, 0.0);
        }

        title = g_strdup_printf("%s - %s", shell->selected_module->name, entry->name);
        shell_set_title(shell, title);
        g_free(title);

        shell_action_set_enabled("RefreshAction", TRUE);
        //shell_action_set_enabled("CopyAction", TRUE);
        shell_status_set_enabled(FALSE);

        /* clear search text when module changes */
        if (module_changed && search_entry) {
            gtk_entry_set_text(GTK_ENTRY(search_entry), "");
        }
    } else {
        shell_status_set_enabled(TRUE);
        shell_set_title(shell, NULL);
        shell_action_set_enabled("RefreshAction", FALSE);
        //shell_action_set_enabled("CopyAction", FALSE);

        gtk_tree_store_clear(GTK_TREE_STORE(shell->info_tree->model));
        set_view_type(SHELL_VIEW_NORMAL, FALSE);

        if (shell->selected_module->summaryfunc) {
	    shell_show_detail_view();
        }
        shell_status_set_enabled(FALSE);
    }
    current = entry;
    updating = FALSE;
}

static void info_selected(GtkTreeSelection * ts, gpointer data)
{
    ShellInfoTree *info = (ShellInfoTree *) data;
    GtkTreeModel *model = GTK_TREE_MODEL(info->sort_model);
    GtkTreeIter parent;
    gchar *datacol, *mi_tag;

    if (!gtk_tree_selection_get_selected(ts, &model, &parent))
	return;

    if (/*shell->view_type == SHELL_VIEW_NORMAL ||*/
        shell->view_type == SHELL_VIEW_PROGRESS) {
        gtk_tree_selection_unselect_all(ts);
        return;
    }

    gtk_tree_model_get(model, &parent, INFO_TREE_COL_DATA, &datacol, -1);
    mi_tag = key_mi_tag(datacol);
    info_selected_show_extra(mi_tag);
    g_free(mi_tag);
}

static ShellInfoTree *info_tree_new(void)
{
    ShellInfoTree *info;
    GtkWidget *treeview, *scroll;
    GtkTreeModel *model;
    GtkTreeModel *sort_model;
    GtkTreeStore *store;
    GtkTreeViewColumn *column;
    GtkCellRenderer *cr_text, *cr_pbuf, *cr_progress;
    GtkTreeSelection *sel;

    info = g_new0(ShellInfoTree, 1);

    scroll = gtk_scrolled_window_new(NULL, NULL);

    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
					(scroll), GTK_SHADOW_IN);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
				   GTK_POLICY_AUTOMATIC,
				   GTK_POLICY_ALWAYS);

    store =
	gtk_tree_store_new(INFO_TREE_NCOL, G_TYPE_STRING, G_TYPE_STRING,
			   G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_FLOAT,
                           G_TYPE_STRING, G_TYPE_STRING);
    model = GTK_TREE_MODEL(store);
    sort_model = gtk_tree_model_sort_new_with_model(model);
    treeview = gtk_tree_view_new_with_model(sort_model);

    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
    gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(treeview), TRUE);

    gtk_widget_add_events(treeview, GDK_BUTTON_PRESS_MASK);
    g_signal_connect(treeview, "button-press-event", G_CALLBACK(on_info_tree_popup_menu), treeview);

    info->col_progress = column = gtk_tree_view_column_new();
    gtk_tree_view_column_set_visible(column, FALSE);
    gtk_tree_view_column_set_min_width(column, 300);
    gtk_tree_view_column_set_clickable(column, TRUE);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    cr_progress = gtk_cell_renderer_progress_new();
    gtk_tree_view_column_pack_start(column, cr_progress, TRUE);
    gtk_tree_view_column_add_attribute(column, cr_progress, "value",
				       INFO_TREE_COL_PROGRESS);
    gtk_tree_view_column_add_attribute(column, cr_progress, "text",
				       INFO_TREE_COL_VALUE);
    gtk_tree_view_column_set_visible(column, FALSE);
    gtk_tree_view_column_set_sort_column_id(column, INFO_TREE_COL_PROGRESS);

    info->col_textvalue = column = gtk_tree_view_column_new();
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
    gtk_tree_view_column_set_clickable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, INFO_TREE_COL_NAME);

    cr_pbuf = gtk_cell_renderer_pixbuf_new();
    gtk_tree_view_column_pack_start(column, cr_pbuf, FALSE);
    gtk_tree_view_column_add_attribute(column, cr_pbuf, "pixbuf",
				       INFO_TREE_COL_PBUF);

    cr_text = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, cr_text, TRUE);
    gtk_tree_view_column_add_attribute(column, cr_text, "markup",
				       INFO_TREE_COL_NAME);

    info->col_extra1 = column = gtk_tree_view_column_new();
    gtk_tree_view_column_set_visible(column, FALSE);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
    gtk_tree_view_column_set_clickable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, INFO_TREE_COL_EXTRA1);

    cr_text = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, cr_text, FALSE);
    gtk_tree_view_column_add_attribute(column, cr_text, "markup",
				       INFO_TREE_COL_EXTRA1);

    info->col_extra2 = column = gtk_tree_view_column_new();
    gtk_tree_view_column_set_visible(column, FALSE);
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
    gtk_tree_view_column_set_clickable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, INFO_TREE_COL_EXTRA2);

    cr_text = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, cr_text, FALSE);
    gtk_tree_view_column_add_attribute(column, cr_text, "markup",
				       INFO_TREE_COL_EXTRA2);

    info->col_value = column = gtk_tree_view_column_new();
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
    gtk_tree_view_column_set_clickable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, INFO_TREE_COL_VALUE);

    cr_text = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, cr_text, FALSE);
    gtk_tree_view_column_add_attribute(column, cr_text, "markup",
				       INFO_TREE_COL_VALUE);

    sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

    g_signal_connect(G_OBJECT(sel), "changed", (GCallback)info_selected, info);

    gtk_container_add(GTK_CONTAINER(scroll), treeview);

    info->scroll = scroll;
    info->view = treeview;
    info->model = model;
    info->sort_model = sort_model;
    info->selection = sel;
    info->natural_sort_columns = 0;
    info->nosort_columns = 0;
    info->default_sort_column = -1;
    info->default_sort_order  = GTK_SORT_ASCENDING;

    gtk_widget_show_all(scroll);

    return info;
}

static ShellTree *tree_new()
{
    ShellTree *shelltree;
    GtkWidget *treeview, *scroll;
    GtkTreeModel *model;
    GtkTreeStore *store;
    GtkCellRenderer *cr_text, *cr_pbuf;
    GtkTreeViewColumn *column;
    GtkTreeSelection *sel;
#if GTK_CHECK_VERSION(3, 0, 0)
    GtkCssProvider *provider;
    provider = gtk_css_provider_new();
#endif

    shelltree = g_new0(ShellTree, 1);

    scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW
					(scroll), GTK_SHADOW_IN);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
				   GTK_POLICY_NEVER,
				   GTK_POLICY_AUTOMATIC);


    store = gtk_tree_store_new(TREE_NCOL, GDK_TYPE_PIXBUF, G_TYPE_STRING,
			       G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_BOOLEAN);
    model = GTK_TREE_MODEL(store);
    treeview = gtk_tree_view_new_with_model(model);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);

#if GTK_CHECK_VERSION(2,12,0)
    gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(treeview), FALSE);
    gtk_tree_view_set_level_indentation(GTK_TREE_VIEW(treeview), 24);
#endif

    column = gtk_tree_view_column_new();
    gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);

    cr_pbuf = gtk_cell_renderer_pixbuf_new();
    cr_text = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, cr_pbuf, FALSE);
    gtk_tree_view_column_pack_start(column, cr_text, TRUE);

    gtk_tree_view_column_add_attribute(column, cr_pbuf, "pixbuf",
				       TREE_COL_PBUF);
    gtk_tree_view_column_add_attribute(column, cr_text, "markup",
				       TREE_COL_NAME);

    sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
    g_signal_connect(G_OBJECT(sel), "changed", (GCallback) module_selected,
		     NULL);

    gtk_container_add(GTK_CONTAINER(scroll), treeview);

#if GTK_CHECK_VERSION(3, 0, 0)
    if(params.theme>0){
        if(params.darkmode){
            gtk_css_provider_load_from_data(provider, "treeview { background-color: rgba(0xa0, 0xa0, 0xa0, 0.1); } treeview:selected { background-color: rgba(0x40, 0x60, 0xff, 1); } ", -1, NULL);
            gtk_style_context_add_provider(gtk_widget_get_style_context(treeview), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
        } else {
            gtk_css_provider_load_from_data(provider, "treeview { background-color: rgba(0x60, 0x60, 0x60, 0.1); } treeview:selected { background-color: rgba(0x40, 0x60, 0xff, 1); } ", -1, NULL);
            gtk_style_context_add_provider(gtk_widget_get_style_context(treeview), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
        }
    }
#endif

    shelltree->scroll = scroll;
    shelltree->view = treeview;
    shelltree->model = model;
    shelltree->modules = NULL;
    shelltree->selection = sel;

    gtk_widget_show_all(scroll);

    return shelltree;
}

gboolean key_is_flagged(const gchar *key) {
    return (key && *key == '$' && strchr(key+1, '$')) ? TRUE : FALSE;
}

gboolean key_is_highlighted(const gchar *key) {
    gchar *flags=NULL;
    key_get_components(key, &flags, NULL, NULL, NULL, NULL);
    if (flags && strchr(flags, '*')) {
        g_free(flags);
        return TRUE;
    }
    g_free(flags);
    return FALSE;
}

gboolean key_wants_details(const gchar *key) {
    gchar *flags=NULL;
    key_get_components(key, &flags, NULL, NULL, NULL, NULL);
    if (flags && strchr(flags, '!')) {
        g_free(flags);
        return TRUE;
    }
    g_free(flags);
    return FALSE;
}

gboolean key_value_has_vendor_string(const gchar *key) {
    gchar *flags=NULL;
    key_get_components(key, &flags, NULL, NULL, NULL, NULL);
    if (flags && strchr(flags, '^')) {
        g_free(flags);
        return TRUE;
    }
    g_free(flags);
    return FALSE;
}

gboolean key_label_is_escaped(const gchar *key) {
    gchar *flags=NULL;
    key_get_components(key, &flags, NULL, NULL, NULL, NULL);
    if (flags && strchr(flags, '@')) {
        g_free(flags);
        return TRUE;
    }
    g_free(flags);
    return FALSE;
}

gchar *key_mi_tag(const gchar *key) {
    static char flag_list[] = "*!^@";
    gchar *p = (gchar*)key, *t;
    const gchar *l;

    if (key_is_flagged(key)) {
        l = strchr(key+1, '$');
        if (*p == '$') p++; /* skip first if exists */
        while(p < l && strchr(flag_list, *p)) {
            p++;
        }
        if (strlen(p)) {
            t = g_strdup(p);
            gchar *dollar = strchr(t, '$');
            if (dollar) *dollar = 0;
            else { g_free(t); return g_strdup(""); }
            return t;
        }
    }
    return NULL;
}

const gchar *key_get_name(const gchar *key) {
    if (key_is_flagged(key))
        return strchr(key+1, '$')+1;
    return key;
}

/* key syntax:
 *  [$[<flags>][<tag>]$]<name>[#[<dis>]]
 *
 * example for key = "$*!Foo$Bar#7":
 * flags = "$*!^Foo$"  // key_is/wants_*() still works on flags
 * tag = "Foo"        // the moreinfo/icon tag
 * name = "Bar#7"     // the full unique name
 * label = "Bar"      // the label displayed
 * dis = "7"
 */
void key_get_components(const gchar *key, gchar **flags, gchar **tag, gchar **name, gchar **label, gchar **dis) {
    if (!key || !*key) return;

    const gchar *np = g_utf8_strchr(key+1, -1, '$');
    if(np) np++;

    if (*key == '$' && np) {
        /* is flagged */
        gchar *f = g_strdup(key);
        gchar *s = g_utf8_strchr(f+1, -1, '$');
	*(s+1) = 0;
	if (flags) *flags = g_strdup(f);
	if (tag) *tag = key_mi_tag(f);
	g_free(f);
    } else {
        np = key;
    }

    if (name) *name = g_strdup(np);
    if (label) {
        *label = g_strdup(np);
        gchar *lbp = g_utf8_strchr(*label, -1, '#');
        if (lbp)
            *lbp = 0;
        if (lbp && dis)
            *dis = g_strdup(lbp + 1);

        if (flags && *flags && strchr(*flags, '@')) {
            gchar *ol = *label;
            *label = g_strcompress(ol);
            g_free(ol);
        }
    }
}
