# SPDX-License-Identifier: GPL-2.0-only
import re
import os
import tuna.tuna_sched as tuna_sched

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk

from tuna import tuna, gui
import procfs

try:
    import perf
except:
    pass

def N_(s):
    """gettext_noop"""
    return s

class process_druid:

    (PROCESS_COL_PID, PROCESS_COL_NAME) = list(range(2))

    def __init__(self, ps, pid, pid_info, nr_cpus, gladefile):
        self.ps = ps
        self.pid = pid
        self.pid_info = pid_info
        self.nr_cpus = nr_cpus
        self.window = Gtk.Builder()
        self.window.add_objects_from_file(gladefile, ("set_process_attributes", "tuna"))
#        self.window = Gtk.glade.XML(gladefile, "set_process_attributes", "tuna")
        self.dialog = self.window.get_object("set_process_attributes")
        pixbuf = self.dialog.render_icon(Gtk.STOCK_PREFERENCES,
                                         Gtk.IconSize.SMALL_TOOLBAR)
        self.dialog.set_icon(pixbuf)
        event_handlers = {
            "on_cmdline_regex_changed" : self.on_cmdline_regex_changed,
            "on_affinity_text_changed" : self.on_affinity_text_changed,
            "on_sched_policy_combo_changed" : self.on_sched_policy_combo_changed,
            "on_command_regex_clicked" : self.on_command_regex_clicked,
            "on_all_these_threads_clicked" : self.on_all_these_threads_clicked,
            "on_just_this_thread_clicked" : self.on_just_this_thread_clicked}
        #self.window.signal_autoconnect(event_handlers)
        self.window.connect_signals(event_handlers)
        self.sched_pri = self.window.get_object("sched_pri_spin")
        self.sched_pri.set_range(0, 99)
        self.sched_policy = self.window.get_object("sched_policy_combo")
        self.regex_edit = self.window.get_object("cmdline_regex")
        self.affinity = self.window.get_object("affinity_text")
        self.just_this_thread = self.window.get_object("just_this_thread")
        self.all_these_threads = self.window.get_object("all_these_threads")
        processes = self.window.get_object("matching_process_list")

        self.sched_pri.set_value(int(pid_info["stat"]["rt_priority"]))
        cmdline_regex = procfs.process_cmdline(pid_info)
        self.affinity_text = tuna.list_to_cpustring(os.sched_getaffinity(pid))
        self.affinity.set_text(self.affinity_text)
        self.create_matching_process_model(processes)
        self.create_policy_model(self.sched_policy)
        self.sched_policy.set_active(os.sched_getscheduler(pid))
        self.regex_edit.set_text(cmdline_regex)
        self.just_this_thread.set_active(True)
        self.regex_edit.set_sensitive(False)
        if pid not in ps or "threads" not in ps[pid]:
            self.all_these_threads.hide()
            self.on_just_this_thread_clicked(None)

    def refresh_match_pids(self, cmdline_regex):
        self.process_list_store.clear()
        for match_pid in self.ps.find_by_cmdline_regex(cmdline_regex):
            info = self.process_list_store.append()
            pid_info = self.ps[match_pid]
            cmdline = procfs.process_cmdline(pid_info)
            self.process_list_store.set(info, self.PROCESS_COL_PID, match_pid,
                                        self.PROCESS_COL_NAME, cmdline)

    def create_matching_process_model(self, processes):
        labels = ["PID", "Name"]

        self.process_list_store = Gtk.ListStore(GObject.TYPE_UINT,
                                                GObject.TYPE_STRING)
        renderer = Gtk.CellRendererText()

        for col in range(len(labels)):
            column = Gtk.TreeViewColumn(labels[col], renderer, text=col)
            column.set_sort_column_id(col)
            processes.append_column(column)

        processes.set_model(self.process_list_store)

    def create_policy_model(self, policy):
        (COL_TEXT, COL_SCHED) = list(range(2))
        list_store = Gtk.ListStore(GObject.TYPE_STRING,
                                   GObject.TYPE_UINT)
        renderer = Gtk.CellRendererText()
        policy.pack_start(renderer, True)
        policy.add_attribute(renderer, "text", COL_TEXT)
        for pol in range(4):
            row = list_store.append()
            list_store.set(row, COL_TEXT, tuna_sched.sched_str(pol),
                           COL_SCHED, pol)
        policy.set_model(list_store)

    def on_cmdline_regex_changed(self, entry):
        process_regex_text = entry.get_text()
        try:
            cmdline_regex = re.compile(process_regex_text)
        except:
            self.process_list_store.clear()
            return
        self.refresh_match_pids(cmdline_regex)

    def on_just_this_thread_clicked(self, button):
        self.regex_edit.set_sensitive(False)
        self.process_list_store.clear()
        info = self.process_list_store.append()
        cmdline = procfs.process_cmdline(self.pid_info)
        self.process_list_store.set(info,
                                    self.PROCESS_COL_PID, self.pid,
                                    self.PROCESS_COL_NAME, cmdline)

    def on_command_regex_clicked(self, button):
        self.regex_edit.set_sensitive(True)
        self.on_cmdline_regex_changed(self.regex_edit)

    def on_all_these_threads_clicked(self, button):
        self.regex_edit.set_sensitive(False)
        self.process_list_store.clear()
        info = self.process_list_store.append()
        cmdline = procfs.process_cmdline(self.ps[self.pid])
        self.process_list_store.set(info,
                                    self.PROCESS_COL_PID, self.pid,
                                    self.PROCESS_COL_NAME, cmdline)
        for tid in list(self.ps[self.pid]["threads"].keys()):
            child = self.process_list_store.append()
            self.process_list_store.set(child,
                                        self.PROCESS_COL_PID, tid,
                                        self.PROCESS_COL_NAME, cmdline)


    def on_sched_policy_combo_changed(self, button):
        new_policy = self.sched_policy.get_active()
        if new_policy in (os.SCHED_FIFO, os.SCHED_RR):
            can_change_pri = True
        else:
            can_change_pri = False
        self.sched_pri.set_sensitive(can_change_pri)

    def on_affinity_text_changed(self, button):
        gui.on_affinity_text_changed(self)

    def set_attributes_for_regex(self, regex, new_policy, new_prio, new_affinity):
        changed = False
        cmdline_regex = re.compile(regex)
        for match_pid in self.ps.find_by_cmdline_regex(cmdline_regex):
            if gui.thread_set_attributes(self.ps[match_pid],
                                         new_policy, new_prio,
                                         new_affinity, self.nr_cpus):
                changed = True

        return changed

    def set_attributes_for_threads(self, pid, new_policy, new_prio, new_affinity):
        changed = False
        threads = self.ps[pid]["threads"]
        for tid in list(threads.keys()):
            if gui.thread_set_attributes(threads[tid], new_policy,
                                         new_prio, new_affinity, self.nr_cpus):
                changed = True

        return changed

    def run(self):
        changed = False
        if self.dialog.run() == Gtk.ResponseType.OK:
            new_policy = int(self.sched_policy.get_active())
            new_prio = int(self.sched_pri.get_value())
            new_affinity = self.affinity.get_text()
            if self.just_this_thread.get_active():
                changed = gui.thread_set_attributes(self.pid_info, new_policy,
                                                    new_prio, new_affinity,
                                                    self.nr_cpus)
            elif self.all_these_threads.get_active():
                if gui.thread_set_attributes(self.pid_info, new_policy,
                                             new_prio, new_affinity,
                                             self.nr_cpus):
                    changed = True
                if self.set_attributes_for_threads(self.pid, new_policy,
                                                   new_prio, new_affinity):
                    changed = True
            else:
                changed = self.set_attributes_for_regex(
                    self.regex_edit.get_text(), new_policy, new_prio,
                    new_affinity)

        self.dialog.destroy()
        return changed

class procview:

    nr_columns = 8
    (COL_PID, COL_POL, COL_PRI, COL_AFF, COL_VOLCTXT, COL_NONVOLCTXT,
     COL_CGROUP, COL_CMDLINE) = list(range(nr_columns))
    columns = (gui.list_store_column(_("PID")),
               gui.list_store_column(_("Policy"), GObject.TYPE_STRING),
               gui.list_store_column(_("Priority")),
               gui.list_store_column(_("Affinity"), GObject.TYPE_STRING),
               gui.list_store_column(_("VolCtxtSwitch"), GObject.TYPE_UINT),
               gui.list_store_column(_("NonVolCtxtSwitch"), GObject.TYPE_UINT),
               gui.list_store_column(_("CGroup"), GObject.TYPE_STRING),
               gui.list_store_column(_("Command Line"), GObject.TYPE_STRING))

    def __init__(self, treeview, ps, show_kthreads, show_uthreads,
                 cpus_filtered, gladefile, disable_perf):
        self.ps = ps
        self.treeview = treeview
        self.nr_cpus = procfs.cpuinfo().nr_cpus
        self.gladefile = gladefile
        self.evlist_added = True

        self.evlist = None
        if not disable_perf:
            try:
                self.perf_init()
            except: # No perf, poll /proc baby, poll
                pass

        if "voluntary_ctxt_switches" not in ps[1]["status"]:
            self.nr_columns = 5
        else:
            self.nr_columns = 7
        try:
            if ps[1]["cgroups"]:
                self.nr_columns = self.nr_columns + 1
        except:
            pass

        self.columns = (gui.list_store_column(_("PID")),
                        gui.list_store_column(_("Policy"), GObject.TYPE_STRING),
                        gui.list_store_column(_("Priority")),
                        gui.list_store_column(_("Affinity"),
                                              GObject.TYPE_STRING))

        if self.nr_columns == 5:
            (self.COL_PID, self.COL_POL, self.COL_PRI, self.COL_AFF,
             self.COL_CMDLINE) = list(range(self.nr_columns))
            self.columns = self.columns \
                + (gui.list_store_column(_("Command Line"), GObject.TYPE_STRING))

        elif self.nr_columns == 6:
            (self.COL_PID, self.COL_POL, self.COL_PRI, self.COL_AFF,
             self.COL_CGROUP, self.COL_CMDLINE) = list(range(self.nr_columns))
            self.columns = self.columns \
                + (gui.list_store_column(_("CGroup"), GObject.TYPE_STRING),
                   gui.list_store_column(_("Command Line"), GObject.TYPE_STRING))
        elif self.nr_columns == 7:
            (self.COL_PID, self.COL_POL, self.COL_PRI, self.COL_AFF, \
                self.COL_VOLCTXT, self.NONVOLCTXT, self.COL_CMDLINE) \
                = list(range(self.nr_columns))
            self.columns = self.columns \
                + (gui.list_store_column(_("VolCtxtSwitch"), GObject.TYPE_UINT),
                   gui.list_store_column(_("NonVolCtxtSwitch"), GObject.TYPE_UINT),
                   gui.list_store_column(_("Command Line"), GObject.TYPE_STRING))

        elif self.nr_columns == 8:
            (self.COL_PID, self.COL_POL, self.COL_PRI, self.COL_AFF, \
                self.COL_VOLCTXT, self.COL_NONVOLCTXT, self.COL_CGROUP, \
                self.COL_CMDLINE) = list(range(self.nr_columns))
            self.columns = self.columns \
                + (gui.list_store_column(_("VolCtxtSwitch"), GObject.TYPE_UINT),
                   gui.list_store_column(_("NonVolCtxtSwitch"), GObject.TYPE_UINT),
                   gui.list_store_column(_("CGroup"), GObject.TYPE_STRING),
                   gui.list_store_column(_("Command Line"), GObject.TYPE_STRING))

        self.tree_store = Gtk.TreeStore(*gui.generate_list_store_columns_with_attr(self.columns))
        self.treeview.set_model(self.tree_store)

        # Allow selecting multiple rows
        selection = treeview.get_selection()
        selection.set_mode(Gtk.SelectionMode.MULTIPLE)

        # Allow enable drag and drop of rows
        self.treeview.enable_model_drag_source(
            Gdk.ModifierType.BUTTON1_MASK, gui.DND_TARGETS,
            Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
        self.treeview.connect("drag_data_get", self.on_drag_data_get_data)
        try:
            self.treeview.connect("query-tooltip", self.on_query_tooltip)
        except:
            # old versions of pygtk2+ doesn't have this signal
            pass

        self.renderer = Gtk.CellRendererText()
        for col in range(self.nr_columns):
            column = Gtk.TreeViewColumn(self.columns[col].name,
                                        self.renderer, text=col)
            column.add_attribute(self.renderer, "weight",
                                 col + self.nr_columns)
            column.set_sort_column_id(col)
            if col == self.COL_CGROUP:
                column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
                column.set_fixed_width(130)
            try:
                self.treeview.set_tooltip_column(col)
            except:
                # old versions of pygtk2+ doesn't have this signal
                pass
            column.set_resizable(True)
            self.treeview.append_column(column)

        self.show_kthreads = show_kthreads
        self.show_uthreads = show_uthreads
        self.cpus_filtered = cpus_filtered
        self.refreshing = True

    def perf_process_events(self, source, condition):
        had_events = True
        while had_events:
            had_events = False
            for cpu in self.cpu_map:
                event = self.evlist.read_on_cpu(cpu)
                if event:
                    had_events = True
                    if event.type == perf.RECORD_FORK:
                        if event.pid == event.tid:
                            try:
                                self.ps.processes[event.pid] = procfs.process(event.pid)
                            except: # short lived thread
                                pass
                        else:
                            if event.pid in self.ps.processes:
                                try:
                                    self.ps.processes[event.pid].threads.processes[event.tid] = procfs.process(event.tid)
                                except (AttributeError, KeyError):
                                    try:
                                        self.ps.processes[event.pid].threads = procfs.pidstats("/proc/%d/task/" % event.pid)
                                    except:
                                        pass
                    elif event.type == perf.RECORD_EXIT:
                        del self.ps[int(event.tid)]
                    elif event.type == perf.RECORD_SAMPLE:
                        tid = event.sample_tid
                        if tid in self.perf_counter:
                            self.perf_counter[tid] += event.sample_period
                        else:
                            self.perf_counter[tid] = event.sample_period

        self.evlist_added = True  # Mark that event arrived, so next periodic show() will refresh GUI
        return True

    def perf_init(self):
        self.cpu_map = perf.cpu_map()
        self.thread_map = perf.thread_map()
        self.evsel_cycles = perf.evsel(task=1, comm=1, wakeup_events=1, \
            watermark=1, sample_type=perf.SAMPLE_CPU | perf.SAMPLE_TID)
        self.evsel_cycles.open(cpus=self.cpu_map, threads=self.thread_map)
        self.evlist = perf.evlist(self.cpu_map, self.thread_map)
        self.evlist.add(self.evsel_cycles)
        self.evlist.mmap()
        self.pollfd = self.evlist.get_pollfd()
        for f in self.pollfd:
            GObject.io_add_watch(f, GObject.IO_IN, self.perf_process_events)
        self.perf_counter = {}

    def on_query_tooltip(self, treeview, x, y, keyboard_mode, tooltip):
        x, y = treeview.convert_widget_to_bin_window_coords(x, y)
        ret = treeview.get_path_at_pos(x, y)
        tooltip.set_text(None)
        if not ret:
            return True
        path, col, xpos, ypos = ret
        if not path:
            return True
        col_id = col.get_sort_column_id()
        if col_id != self.COL_CMDLINE:
            return True
        row = self.tree_store.get_iter(path)
        if not row:
            return True
        pid = int(self.tree_store.get_value(row, self.COL_PID))
        if not tuna.iskthread(pid):
            return True
        cmdline = self.tree_store.get_value(row, self.COL_CMDLINE).split(' ')[0]
        help = tuna.kthread_help(cmdline)
        tooltip.set_markup("<b>%s %d(%s)</b>\n%s" % \
			(_("Kernel Thread"), pid, cmdline, _(help)))
        return True

    def foreach_selected_cb(self, model, path, iter, pid_list):
        pid = model.get_value(iter, self.COL_PID)
        pid_list.append(str(pid))

    def on_drag_data_get_data(self, treeview, context, selection,
                              target_id, etime):
        treeselection = treeview.get_selection()
        pid_list = []
        treeselection.selected_foreach(self.foreach_selected_cb, pid_list)
        selection.set(selection.target, 8, "pid:" + ",".join(pid_list))

    def set_thread_columns(self, iter, tid, thread_info):
        new_value = [None] * self.nr_columns

        new_value[self.COL_PRI] = int(thread_info["stat"]["rt_priority"])
        new_value[self.COL_POL] = tuna_sched.sched_str(os.sched_getscheduler(tid))[6:]
        thread_affinity_list = os.sched_getaffinity(tid)

        new_value[self.COL_PID] = tid
        new_value[self.COL_AFF] = tuna.list_to_cpustring(thread_affinity_list)
        try:
            new_value[self.COL_VOLCTXT] = int(thread_info["status"]["voluntary_ctxt_switches"])
            new_value[self.COL_NONVOLCTXT] \
                = int(thread_info["status"]["nonvoluntary_ctxt_switches"])
            new_value[self.COL_CGROUP] = thread_info["cgroups"]
        except:
            pass

        new_value[self.COL_CMDLINE] = procfs.process_cmdline(thread_info)
        gui.set_store_columns(self.tree_store, iter, new_value)

    def show(self, force_refresh=False):
        # Start with the first row, if there is one, on the
        # process list. If the first time update_rows will just
        # have everything in new_tids and append_new_tids will
        # create the rows.
        if not self.refreshing and not force_refresh:
            return

        # If using perf only refresh if we saw at least a new event since last refresh
        if not self.evlist or self.evlist_added:
            self.evlist_added = None

            row = self.tree_store.get_iter_first()
            self.update_rows(self.ps, row, None)
            self.treeview.show_all()

    def update_rows(self, threads, row, parent_row):
        new_tids = list(threads.keys())
        previous_row = None
        while row:
            tid = self.tree_store.get_value(row, self.COL_PID)
            if previous_row:
                previous_tid = self.tree_store.get_value(previous_row, self.COL_PID)
                if previous_tid == tid:
                    # print "WARNING: tree_store dup %d, fixing..." % tid
                    self.tree_store.remove(previous_row)
            if tid not in threads:
                if self.tree_store.remove(row):
                    # removed and now row is the next one
                    continue
                # removed and its the last one
                break
            try:
                new_tids.remove(tid)
            except:
                # FIXME: understand in what situation this
                # can happen, seems harmless from visual
                # inspection.
                pass
            if tuna.thread_filtered(tid, self.cpus_filtered,
                                    self.show_kthreads, self.show_uthreads):
                if self.tree_store.remove(row):
                    # removed and now row is the next one
                    continue
                    # removed and its the last one
                break
            else:
                try:
                    self.set_thread_columns(row, tid, threads[tid])
                    if "threads" in threads[tid]:
                        children = threads[tid]["threads"]
                    else:
                        children = {}
                    child_row = self.tree_store.iter_children(row)
                    self.update_rows(children, child_row, row)
                except: # thread doesn't exists anymore
                    if self.tree_store.remove(row):
                        # removed and now row is the next one
                        continue
                    # removed and its the last one
                    break

            previous_row = row
            row = self.tree_store.iter_next(row)

        new_tids.sort()
        self.append_new_tids(parent_row, threads, new_tids)

    def append_new_tids(self, parent_row, threads, tid_list):
        for tid in tid_list:
            if tuna.thread_filtered(tid, self.cpus_filtered, self.show_kthreads,
                                    self.show_uthreads):
                continue

            row = self.tree_store.append(parent_row)

            try:
                self.set_thread_columns(row, tid, threads[tid])
            except: # Thread doesn't exists anymore
                self.tree_store.remove(row)
                continue

            if "threads" in threads[tid]:
                children = threads[tid]["threads"]
                children_list = sorted(children.keys())
                for child in children_list:
                    child_row = self.tree_store.append(row)
                    try:
                        self.set_thread_columns(child_row, child,
                                                children[child])
                    except: # Thread doesn't exists anymore
                        self.tree_store.remove(child_row)

    def refresh(self):
        self.ps.reload()
        self.ps.reload_threads()

        self.show(True)

    def edit_attributes(self, a):
        ret = self.treeview.get_path_at_pos(self.last_x, self.last_y)
        if not ret:
            return
        path, col, xpos, ypos = ret
        if not path:
            return
        row = self.tree_store.get_iter(path)
        pid = self.tree_store.get_value(row, self.COL_PID)
        if pid in self.ps:
            pid_info = self.ps[pid]
        else:
            parent = self.tree_store.iter_parent(row)
            ppid = self.tree_store.get_value(parent, self.COL_PID)
            pid_info = self.ps[ppid].threads[pid]

        dialog = process_druid(self.ps, pid, pid_info, self.nr_cpus,
                               self.gladefile)
        if dialog.run():
            self.refresh()

    def kthreads_view_toggled(self, a):
        self.show_kthreads = not self.show_kthreads
        self.show(True)

    def uthreads_view_toggled(self, a):
        self.show_uthreads = not self.show_uthreads
        self.show(True)

    def help_dialog(self, a):
        ret = self.treeview.get_path_at_pos(self.last_x, self.last_y)
        if not ret:
            return
        path, col, xpos, ypos = ret
        if not path:
            return
        row = self.tree_store.get_iter(path)
        pid = self.tree_store.get_value(row, self.COL_PID)
        if pid not in self.ps:
            return

        cmdline = self.tree_store.get_value(row, self.COL_CMDLINE)
        help, title = tuna.kthread_help_plain_text(pid, cmdline)

        dialog = Gtk.MessageDialog(None, \
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, \
            Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _(help))
        dialog.set_title(title)
        ret = dialog.run()
        dialog.destroy()

    def refresh_toggle(self, a):
        self.refreshing = not self.refreshing

    def save_kthreads_tunings(self, a):
        dialog = Gtk.FileChooserDialog(_("Save As"), None, \
            Gtk.FileChooserAction.SAVE, \
            (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, \
            Gtk.STOCK_OK, Gtk.ResponseType.OK))
        dialog.set_default_response(Gtk.ResponseType.OK)

        try:
            dialog.set_do_overwrite_confirmation(True)
        except:
            pass

        filter = Gtk.FileFilter()
        filter.set_name("rtctl config files")
        filter.add_pattern("*.rtctl")
        filter.add_pattern("*.tuna")
        filter.add_pattern("*rtgroup*")
        dialog.add_filter(filter)

        filter = Gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        dialog.add_filter(filter)

        response = dialog.run()

        filename = dialog.get_filename()
        dialog.destroy()

        if response != Gtk.ResponseType.OK:
            return

        self.refresh()
        kthreads = tuna.get_kthread_sched_tunings(self.ps)
        tuna.generate_rtgroups(filename, kthreads, self.nr_cpus)

        if filename != "/etc/rtgroups":
            dialog = Gtk.MessageDialog(None, \
                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, \
                Gtk.MessageType.INFO, Gtk.ButtonsType.YES_NO, \
                "Kernel thread tunings saved!\n\n" \
                "Now you can use it with rtctl:\n\n" \
                "rtctl --file %s reset\n\n" \
                "If you want the changes to be in " \
                "effect every time you boot the system " \
                "please move %s to /etc/rtgroups\n\n" \
                "Do you want to do that now?" % (filename, filename))
            response = dialog.run()
            dialog.destroy()
            if response == Gtk.ResponseType.YES:
                filename = "/etc/rtgroups"
                tuna.generate_rtgroups(filename, kthreads, self.nr_cpus)

        dialog = Gtk.MessageDialog(None, \
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, \
            Gtk.MessageType.INFO, Gtk.ButtonsType.OK, \
            _("Kernel thread tunings saved to %s!") % filename)
        dialog.run()
        dialog.destroy()

    def on_processlist_button_press_event(self, treeview, event):
        if event.type != Gdk.EventType.BUTTON_PRESS or event.button != 3:
            return

        self.last_x = int(event.x)
        self.last_y = int(event.y)

        menu = Gtk.Menu()

        setattr = Gtk.MenuItem(_("_Set process attributes"), use_underline = True)
        if self.refreshing:
            refresh = Gtk.MenuItem(_("Sto_p refreshing the process list"), use_underline = True)
        else:
            refresh = Gtk.MenuItem(_("_Refresh the process list"), use_underline = True)

        if self.show_kthreads:
            kthreads = Gtk.MenuItem(_("_Hide kernel threads"), use_underline = True)
        else:
            kthreads = Gtk.MenuItem(_("_Show kernel threads"), use_underline = True)

        if self.show_uthreads:
            uthreads = Gtk.MenuItem(_("_Hide user threads"), use_underline = True)
        else:
            uthreads = Gtk.MenuItem(_("_Show user threads"), use_underline = True)

        help = Gtk.MenuItem(_("_What is this?"), use_underline = True)

        save_kthreads_tunings = Gtk.MenuItem(_("_Save kthreads tunings"), use_underline = True)

        menu.add(save_kthreads_tunings)
        menu.add(setattr)
        menu.add(refresh)
        menu.add(kthreads)
        menu.add(uthreads)
        menu.add(help)

        save_kthreads_tunings.connect_object(
            'activate', self.save_kthreads_tunings, event)
        setattr.connect_object('activate', self.edit_attributes, event)
        refresh.connect_object('activate', self.refresh_toggle, event)
        kthreads.connect_object('activate', self.kthreads_view_toggled, event)
        uthreads.connect_object('activate', self.uthreads_view_toggled, event)
        help.connect_object('activate', self.help_dialog, event)

        save_kthreads_tunings.show()
        setattr.show()
        refresh.show()
        kthreads.show()
        uthreads.show()
        help.show()

        menu.popup(None, None, None, event, event.button, event.time)

    def toggle_mask_cpu(self, cpu, enabled):
        if not enabled:
            if cpu not in self.cpus_filtered:
                self.cpus_filtered.append(cpu)
                self.show(True)
        else:
            if cpu in self.cpus_filtered:
                self.cpus_filtered.remove(cpu)
                self.show(True)
