diff --git a/po/POTFILES b/po/POTFILES index 4c11296c8..fd7e9f425 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -6,8 +6,9 @@ src/Indicator/Widgets/PopoverWidget.vala src/Views/ProcessView/ProcessInfoView/ProcessInfoHeader.vala src/Views/ProcessView/ProcessInfoView/ProcessInfoIOStats.vala src/Views/ProcessView/ProcessInfoView/ProcessInfoView.vala -src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala src/Views/ProcessView/ProcessInfoView/ProcessInfoCPURAM.vala +src/Views/ProcessView/ProcessTreeView/ProcessTreeView.vala +src/Views/ProcessView/ProcessTreeView/ProcessTreeViewNameCell.vala src/Views/PreferencesView.vala src/Views/SystemView/SystemCPUView.vala src/Views/SystemView/SystemCPUInfoPopover.vala diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 6b464716d..e80cdd0e7 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -91,10 +91,10 @@ public class Monitor.MainWindow : Gtk.ApplicationWindow { search_revealer.reveal_child = stack.visible_child == process_view; }); - new Thread ("upd", () => { - Timeout.add_seconds (MonitorApp.settings.get_int ("update-time"), () => { - process_view.update (); + var process_manager = ProcessManager.get_default (); + Timeout.add_seconds (MonitorApp.settings.get_int ("update-time"), () => { + process_manager.update (); Idle.add (() => { system_view.update (); dbusserver.indicator_state (MonitorApp.settings.get_boolean ("indicator-state")); @@ -103,8 +103,7 @@ public class Monitor.MainWindow : Gtk.ApplicationWindow { dbusserver.update (res); return false; }); - return true; - }); + return true; }); dbusserver.indicator_state (MonitorApp.settings.get_boolean ("indicator-state")); @@ -112,22 +111,10 @@ public class Monitor.MainWindow : Gtk.ApplicationWindow { MonitorApp.settings.bind ("opened-view", stack, "visible-child-name", DEFAULT); search_entry.search_changed.connect (() => { - // collapse tree only when search is focused and changed - if (search_entry.is_focus ()) { - process_view.process_tree_view.collapse_all (); - } - - process_view.needle = search_entry.text; - - // focus on child row to avoid the app crashes by clicking "Kill/End Process" buttons in headerbar - process_view.process_tree_view.focus_on_child_row (); + process_view.treeview_model.filtered.needle = search_entry.text; search_entry.grab_focus (); }); - search_entry.activate.connect (() => { - process_view.process_tree_view.focus_on_first_row (); - }); - var search_action = new GLib.SimpleAction ("search", null); search_action.activate.connect (() => { search_entry.text = ""; diff --git a/src/Managers/ProcessManager.vala b/src/Managers/ProcessManager.vala index 69033027c..f86cc66f4 100644 --- a/src/Managers/ProcessManager.vala +++ b/src/Managers/ProcessManager.vala @@ -4,7 +4,7 @@ */ namespace Monitor { - public class ProcessManager { + public class ProcessManager : GLib.Object { private static GLib.Once instance; public static unowned ProcessManager get_default () { return instance.once (() => { return new ProcessManager (); }); @@ -33,7 +33,7 @@ namespace Monitor { apps_info_list = new Gee.HashMap (); populate_apps_info (); - update_processes.begin (); + update (); } public void populate_apps_info () { @@ -102,7 +102,7 @@ namespace Monitor { /** * Gets all new process and adds them */ - public async void update_processes () { + public void update_processes () { /* CPU */ GTop.Cpu cpu_data; GTop.get_cpu (out cpu_data); @@ -158,6 +158,18 @@ namespace Monitor { } + public void update () { + new Thread ("update-processes", () => { + Idle.add (() => { + update_processes (); + return false; + }); + return true; + }); + /* emit the updated signal so that subscribers can update */ + updated (); + } + /** Sets name and icon for a process that is a Flatpak app and its children. */ private void set_flatpak_name_icon (Process process, GLib.Icon icon, string name) { process.application_name = name; diff --git a/src/Models/ProcessRowData.vala b/src/Models/ProcessRowData.vala new file mode 100644 index 000000000..369c2973a --- /dev/null +++ b/src/Models/ProcessRowData.vala @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +/* This class holds data from Process class to use in the ColumnView */ +public class Monitor.ProcessRowData : GLib.Object { + public Icon icon { get; set; } + public string name { get; set; } + public int cpu { get; set; } + public uint64 memory { get; set; } + public int pid { get; set; } + public string cmd { get; set; } + public Gee.HashMap bindings = new Gee.HashMap (); +} diff --git a/src/Models/TreeViewFilter.vala b/src/Models/TreeViewFilter.vala new file mode 100644 index 000000000..c73e0a597 --- /dev/null +++ b/src/Models/TreeViewFilter.vala @@ -0,0 +1,41 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +public class Monitor.TreeViewFilter : GLib.Object { + public string needle { get; set; } + public Gtk.FilterListModel model_out; + + public TreeViewFilter (GLib.ListModel? model) { + var name_filter = build_str_filter ("name"); + var cmd_filter = build_str_filter ("cmd"); + + // since the pid property is an int, we need to use a custom filter to convert it to a string + var pid_filter = new Gtk.CustomFilter ((obj) => { + var item = (ProcessRowData) obj; + bool pid_found = item.pid.to_string ().contains (needle.casefold ()); + return pid_found; + }); + + var any_filter = new Gtk.AnyFilter (); + any_filter.append (name_filter); + any_filter.append (cmd_filter); + any_filter.append (pid_filter); + + model_out = new Gtk.FilterListModel (model, any_filter); + + bind_property ("needle", name_filter, "search", SYNC_CREATE); + bind_property ("needle", cmd_filter, "search", SYNC_CREATE); + } + + private Gtk.StringFilter build_str_filter (string column_name) { + var expression = new Gtk.PropertyExpression (typeof (ProcessRowData), null, column_name); + return new Gtk.StringFilter (expression) { + ignore_case = true, + match_mode = SUBSTRING, + search = needle + }; + } + +} diff --git a/src/Models/TreeViewModel.vala b/src/Models/TreeViewModel.vala index aecb1c760..15c566785 100644 --- a/src/Models/TreeViewModel.vala +++ b/src/Models/TreeViewModel.vala @@ -3,30 +3,49 @@ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) */ -public enum Monitor.Column { - ICON, - NAME, - CPU, - MEMORY, - PID, - CMD -} +public class Monitor.TreeViewModel : GLib.Object { -public class Monitor.TreeViewModel : Gtk.TreeStore { public ProcessManager process_manager; - private Gee.Map process_rows; + + public TreeViewFilter filtered { get; private set; } + public Gtk.SingleSelection selection_model { get; private set; } + public signal void added_first_row (); + public signal void process_selected (Process process); + + public Gtk.Sorter sorter { + get { + return sorted.sorter; + } + set { + sorted.sorter = value; + } + } + + private GLib.ListStore store; + private Gtk.SortListModel sorted; + + private Gee.Map process_rows; construct { - process_rows = new Gee.HashMap (); - - set_column_types (new Type[] { - typeof (string), - typeof (string), - typeof (double), - typeof (int64), - typeof (int), - typeof (string), + process_rows = new Gee.HashMap (); + store = new GLib.ListStore (typeof (ProcessRowData)); + sorted = new Gtk.SortListModel (store, null); + + filtered = new TreeViewFilter (sorted); + + selection_model = new Gtk.SingleSelection (filtered.model_out) { + autoselect = true + }; + + selection_model.notify["selected-item"].connect ((sender, property) => { + var row_data = (ProcessRowData) selection_model.get_selected_item (); + // prevent passing null when there is no more processes left after filtering + if (row_data == null) { + return; + } + Process process = process_manager.get_process (row_data.pid); + process_selected (process); }); process_manager = ProcessManager.get_default (); @@ -37,6 +56,16 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { Idle.add (() => { add_running_processes (); return false; }); } + public Gtk.StringSorter str_sorter (string column_name) { + return new Gtk.StringSorter (new Gtk.PropertyExpression (typeof (ProcessRowData), null, column_name)); + } + + public Gtk.NumericSorter num_sorter (string column_name) { + return new Gtk.NumericSorter (new Gtk.PropertyExpression (typeof (ProcessRowData), null, column_name)) { + sort_order = Gtk.SortType.DESCENDING + }; + } + private void add_running_processes () { debug ("add_running_processes"); var running_processes = process_manager.get_process_list (); @@ -49,24 +78,22 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { if (process != null && !process_rows.has_key (process.stat.pid)) { debug ("Add process %d Parent PID: %d", process.stat.pid, process.stat.ppid); // add the process to the model - Gtk.TreeIter iter; - append (out iter, null); // null means top-level - - // donno what is going on, but maybe just use a string instead of Icon ?? - // coz it lagz - // string icon_name = process.icon.to_string (); - - set (iter, - Column.NAME, process.application_name, - Column.ICON, process.icon.to_string (), - Column.PID, process.stat.pid, - Column.CMD, process.command, - -1); + var row = new ProcessRowData () { + icon = process.icon, + name = process.application_name, + cpu = (int) process.cpu_percentage, + memory = process.mem_usage, + pid = process.stat.pid, + cmd = process.command + }; + + store.append (row); + if (process_rows.size < 1) { added_first_row (); } // add the process to our cache of process_rows - process_rows.set (process.stat.pid, iter); + process_rows.set (process.stat.pid, row); return true; } return false; @@ -75,11 +102,17 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { private void update_model () { foreach (int pid in process_rows.keys) { Process process = process_manager.get_process (pid); - Gtk.TreeIter iter = process_rows[pid]; - set (iter, - Column.CPU, process.cpu_percentage, - Column.MEMORY, process.mem_usage, - -1); + var process_row = process_rows.get (pid); + + uint pos; + if (!store.find (process_row, out pos)) { + return; + } + + var item = (ProcessRowData) store.get_item (pos); + item.cpu = (int) process.cpu_percentage; + item.memory = process.mem_usage; + sorter.changed (DIFFERENT); } } @@ -87,8 +120,11 @@ public class Monitor.TreeViewModel : Gtk.TreeStore { debug ("remove process %d from model".printf (pid)); // if process rows has pid if (process_rows.has_key (pid)) { - var cached_iter = process_rows.get (pid); - remove (ref cached_iter); + uint pos; + var process_row = process_rows.get (pid); + if (store.find (process_row, out pos)) { + store.remove (pos); + } process_rows.unset (pid); } } diff --git a/src/Monitor.vala b/src/Monitor.vala index 5a71ae5f3..ebc349422 100644 --- a/src/Monitor.vala +++ b/src/Monitor.vala @@ -127,8 +127,6 @@ namespace Monitor { } settings.bind ("is-maximized", window, "maximized", SettingsBindFlags.SET); - - window.process_view.process_tree_view.focus_on_first_row (); } public static int main (string[] args) { diff --git a/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala b/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala deleted file mode 100644 index 51d3c2ef2..000000000 --- a/src/Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2025 elementary, Inc. (https://elementary.io) - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -public class Monitor.CPUProcessTreeView : Gtk.TreeView { - private new TreeViewModel model; - private Gtk.TreeViewColumn name_column; - private Gtk.TreeViewColumn pid_column; - private Gtk.TreeViewColumn cpu_column; - private Gtk.TreeViewColumn memory_column; - - public signal void process_selected (Process process); - - public CPUProcessTreeView (TreeViewModel model) { - this.model = model; - - enable_search = false; - - // setup name column - name_column = new Gtk.TreeViewColumn (); - name_column.title = _("Process Name"); - name_column.expand = true; - name_column.min_width = 250; - name_column.set_sort_column_id (Column.NAME); - - var icon_cell = new Gtk.CellRendererPixbuf (); - name_column.pack_start (icon_cell, false); - // name_column.add_attribute (icon_cell, "icon_name", Column.ICON); - name_column.set_cell_data_func (icon_cell, icon_cell_layout); - - var name_cell = new Gtk.CellRendererText (); - name_cell.ellipsize = Pango.EllipsizeMode.END; - name_cell.set_fixed_height_from_font (1); - name_column.pack_start (name_cell, false); - name_column.add_attribute (name_cell, "text", Column.NAME); - insert_column (name_column, -1); - - // setup cpu column - var cpu_cell = new Gtk.CellRendererText (); - cpu_cell.xalign = 0.5f; - - cpu_column = new Gtk.TreeViewColumn.with_attributes (_("CPU"), cpu_cell); - cpu_column.expand = false; - cpu_column.set_cell_data_func (cpu_cell, cpu_usage_cell_layout); - cpu_column.alignment = 0.5f; - cpu_column.set_sort_column_id (Column.CPU); - insert_column (cpu_column, -1); - - // setup memory column - var memory_cell = new Gtk.CellRendererText (); - memory_cell.xalign = 0.5f; - - memory_column = new Gtk.TreeViewColumn.with_attributes (_("Memory"), memory_cell); - memory_column.expand = false; - memory_column.set_cell_data_func (memory_cell, memory_usage_cell_layout); - memory_column.alignment = 0.5f; - memory_column.set_sort_column_id (Column.MEMORY); - insert_column (memory_column, -1); - - // setup PID column - var pid_cell = new Gtk.CellRendererText (); - pid_cell.xalign = 0.5f; - pid_column = new Gtk.TreeViewColumn.with_attributes (_("PID"), pid_cell); - pid_column.set_cell_data_func (pid_cell, pid_cell_layout); - pid_column.expand = false; - pid_column.alignment = 0.5f; - pid_column.set_sort_column_id (Column.PID); - pid_column.add_attribute (pid_cell, "text", Column.PID); - insert_column (pid_column, -1); - - // resize all of the columns - columns_autosize (); - - set_model (model); - - model.added_first_row.connect (() => { - focus_on_first_row (); - }); - - cursor_changed.connect (_cursor_changed); - // model.process_manager.updated.connect (_cursor_changed); - - var key_controller = new Gtk.EventControllerKey (); - add_controller (key_controller); - key_controller.key_released.connect ((keyval, keycode, state) => { - switch (keyval) { - case Gdk.Key.Left: - collapse (); - break; - case Gdk.Key.Right: - expanded (); - break; - } - }); - } - public void icon_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer icon_cell, Gtk.TreeModel model, Gtk.TreeIter iter) { - Value icon_name; - model.get_value (iter, Column.ICON, out icon_name); - string path = ((string) icon_name); - - ((Gtk.CellRendererPixbuf)icon_cell).icon_name = path; - } - - public void cpu_usage_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) { - // grab the value that was store in the model and convert it down to a usable format - Value cpu_usage_value; - model.get_value (iter, Column.CPU, out cpu_usage_value); - double cpu_usage = cpu_usage_value.get_double (); - - // format the double into a string - if (cpu_usage < 0.0) - ((Gtk.CellRendererText)cell).text = Utils.NO_DATA; - else - ((Gtk.CellRendererText)cell).text = "%.0f%%".printf (cpu_usage); - } - - public void memory_usage_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) { - // grab the value that was store in the model and convert it down to a usable format - Value memory_usage_value; - model.get_value (iter, Column.MEMORY, out memory_usage_value); - int64 memory_usage = memory_usage_value.get_int64 (); - double memory_usage_double = (double) memory_usage; - string units = _("KiB"); - - // convert to MiB if needed - if (memory_usage_double > 1024.0) { - memory_usage_double /= 1024.0; - units = _("MiB"); - } - - // convert to GiB if needed - if (memory_usage_double > 1024.0) { - memory_usage_double /= 1024.0; - units = _("GiB"); - } - - // format the double into a string - if (memory_usage == 0) - ((Gtk.CellRendererText)cell).text = Utils.NO_DATA; - else - ((Gtk.CellRendererText)cell).text = "%.1f %s".printf (memory_usage_double, units); - } - - private void pid_cell_layout (Gtk.CellLayout cell_layout, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) { - Value pid_value; - model.get_value (iter, Column.PID, out pid_value); - int pid = pid_value.get_int (); - // format the double into a string - if (pid == 0) { - ((Gtk.CellRendererText)cell).text = Utils.NO_DATA; - } - } - - public void focus_on_first_row () { - Gtk.TreePath tree_path = new Gtk.TreePath.from_indices (0); - this.set_cursor (tree_path, null, false); - grab_focus (); - } - - public void focus_on_child_row () { - Gtk.TreePath tree_path = new Gtk.TreePath.from_indices (0, 0); - this.set_cursor (tree_path, null, false); - grab_focus (); - } - - public int get_pid_of_selected () { - Gtk.TreeIter iter; - Gtk.TreeModel model; - int pid = 0; - var selection = this.get_selection ().get_selected_rows (out model).nth_data (0); - model.get_iter (out iter, selection); - model.get (iter, Column.PID, out pid); - return pid; - } - - // How about GtkTreeSelection ? - - public void expanded () { - Gtk.TreeModel model; - var selection = this.get_selection ().get_selected_rows (out model).nth_data (0); - this.expand_row (selection, false); - } - - public void collapse () { - Gtk.TreeModel model; - var selection = this.get_selection ().get_selected_rows (out model).nth_data (0); - this.collapse_row (selection); - } - - public void kill_process () { - int pid = get_pid_of_selected (); - model.kill_process (pid); - } - - public void end_process () { - int pid = get_pid_of_selected (); - model.end_process (pid); - } - - // when row is selected send signal to update process_info_view - public void _cursor_changed () { - Gtk.TreeModel tree_model; - Gtk.TreeIter iter; - int pid = 0; - var selection = get_selection ().get_selected_rows (out tree_model).nth_data (0); - - if (selection != null) { - tree_model.get_iter (out iter, selection); - tree_model.get (iter, Column.PID, out pid); - Process process = model.process_manager.get_process (pid); - process_selected (process); - debug ("cursor changed"); - } - } - -} diff --git a/src/Views/ProcessView/ProcessTreeView/ProcessTreeView.vala b/src/Views/ProcessView/ProcessTreeView/ProcessTreeView.vala new file mode 100644 index 000000000..dc790d1d8 --- /dev/null +++ b/src/Views/ProcessView/ProcessTreeView/ProcessTreeView.vala @@ -0,0 +1,166 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +public class Monitor.ProcessTreeView : Granite.Bin { + + public ProcessTreeView (TreeViewModel model) { + var column_view = new Gtk.ColumnView (model.selection_model) { + name = "monitor-process-column-view", + reorderable = false, + hexpand = true, + vexpand = true + }; + model.sorter = column_view.sorter; + + var name_item_factory = new Gtk.SignalListItemFactory (); + name_item_factory.setup.connect (name_item_factory_setup); + name_item_factory.bind.connect (name_item_factory_bind); + name_item_factory.unbind.connect (name_item_factory_unbind); + + var cpu_item_factory = new Gtk.SignalListItemFactory (); + cpu_item_factory.setup.connect (generic_item_factory_setup); + cpu_item_factory.bind.connect (cpu_item_factory_bind); + cpu_item_factory.unbind.connect (cpu_item_factory_unbind); + + var memory_item_factory = new Gtk.SignalListItemFactory (); + memory_item_factory.setup.connect (generic_item_factory_setup); + memory_item_factory.bind.connect (memory_item_factory_bind); + memory_item_factory.unbind.connect (memory_item_factory_unbind); + + var pid_item_factory = new Gtk.SignalListItemFactory (); + pid_item_factory.setup.connect (generic_item_factory_setup); + pid_item_factory.bind.connect (pid_item_factory_bind); + pid_item_factory.unbind.connect (pid_item_factory_unbind); + + var name_column = new Gtk.ColumnViewColumn (_("Process Name"), name_item_factory) { + sorter = model.str_sorter ("name"), + expand = true + }; + column_view.append_column (name_column); + + var cpu_column = new Gtk.ColumnViewColumn (_("CPU"), cpu_item_factory) { + sorter = model.num_sorter ("cpu"), + expand = false + }; + column_view.append_column (cpu_column); + + var mem_column = new Gtk.ColumnViewColumn (_("Memory"), memory_item_factory) { + sorter = model.num_sorter ("memory"), + expand = false + }; + column_view.append_column (mem_column); + + var pid_column = new Gtk.ColumnViewColumn (_("PID"), pid_item_factory) { + sorter = model.num_sorter ("pid"), + expand = false + }; + column_view.append_column (pid_column); + + var scrolled_window = new Gtk.ScrolledWindow () { + child = column_view + }; + child = scrolled_window; + } + + private void generic_item_factory_setup (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var label = new Gtk.Label (Utils.NO_DATA) { + hexpand = true, + halign = START + }; + cell.child = label; + } + + private void name_item_factory_setup (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var name_cell = new ProcessTreeViewNameCell (); + cell.child = name_cell; + } + + private void name_item_factory_bind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var name_cell = (ProcessTreeViewNameCell) cell.child; + var label = name_cell.label; + var icon = name_cell.icon; + + var item = (ProcessRowData) cell.item; + + var binding_name = item.bind_property ("name", label, "label", SYNC_CREATE); + item.bindings.set ("name", binding_name); + + var binding_icon = item.bind_property ("icon", icon, "gicon", SYNC_CREATE); + item.bindings.set ("icon", binding_icon); + } + + private void name_item_factory_unbind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var name_cell = (ProcessTreeViewNameCell) cell.child; + var label = name_cell.label; + var icon = name_cell.icon; + label.label = null; + icon.gicon = null; + ((ProcessRowData) cell.item).bindings["name"].unbind (); + ((ProcessRowData) cell.item).bindings["icon"].unbind (); + } + + private void cpu_item_factory_bind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var label = (Gtk.Label) cell.child; + var item = (ProcessRowData) cell.item; + var binding_cpu = item.bind_property ("cpu", label, "label", SYNC_CREATE, (_, from_val, ref to_val) => { + int percentage = from_val.get_int (); + to_val.set_string ("%.0f%%".printf (percentage)); + return true; + }); + item.bindings.set ("cpu", binding_cpu); + } + + private void cpu_item_factory_unbind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var label = (Gtk.Label) cell.child; + var item = (ProcessRowData) cell.item; + label.label = null; + item.bindings["cpu"].unbind (); + } + + private void memory_item_factory_bind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var label = (Gtk.Label) cell.child; + var item = (ProcessRowData) cell.item; + var binding_memory = item.bind_property ("memory", label, "label", SYNC_CREATE, (_, from_val, ref to_val) => { + to_val.set_string (format_size (from_val.get_uint64 () * 1024, IEC_UNITS)); + return true; + }); + item.bindings.set ("memory", binding_memory); + } + + private void memory_item_factory_unbind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var label = (Gtk.Label) cell.child; + var item = (ProcessRowData) cell.item; + label.label = null; + item.bindings["memory"].unbind (); + } + + private void pid_item_factory_bind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var label = (Gtk.Label) cell.child; + var item = (ProcessRowData) cell.item; + var binding_pid = item.bind_property ("pid", label, "label", SYNC_CREATE, (_, from_val, ref to_val) => { + to_val.set_string ("%d".printf (from_val.get_int ())); + return true; + }); + item.bindings.set ("pid", binding_pid); + } + + private void pid_item_factory_unbind (Object object) { + var cell = (Gtk.ColumnViewCell) object; + var label = (Gtk.Label) cell.child; + var item = (ProcessRowData) cell.item; + label.label = null; + item.bindings["pid"].unbind (); + } + +} diff --git a/src/Views/ProcessView/ProcessTreeView/ProcessTreeViewNameCell.vala b/src/Views/ProcessView/ProcessTreeView/ProcessTreeViewNameCell.vala new file mode 100644 index 000000000..f7e6a460b --- /dev/null +++ b/src/Views/ProcessView/ProcessTreeView/ProcessTreeViewNameCell.vala @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +public class Monitor.ProcessTreeViewNameCell : Gtk.Box { + public Gtk.Image icon; + public Gtk.Label label; + + public ProcessTreeViewNameCell () { + hexpand = true; + halign = START; + + icon = new Gtk.Image.from_icon_name ("application-x-executable") { + pixel_size = 16 + }; + label = new Gtk.Label (Utils.NO_DATA); + + append (icon); + append (label); + } + +} diff --git a/src/Views/ProcessView/ProcessView.vala b/src/Views/ProcessView/ProcessView.vala index 0b7abda9d..6777c8db1 100644 --- a/src/Views/ProcessView/ProcessView.vala +++ b/src/Views/ProcessView/ProcessView.vala @@ -4,12 +4,10 @@ */ public class Monitor.ProcessView : Granite.Bin { - public string needle = ""; - - public CPUProcessTreeView process_tree_view { get; private set; } + public ProcessTreeView process_tree_view { get; private set; } private ProcessInfoView process_info_view; - private TreeViewModel treeview_model; + public TreeViewModel treeview_model { get; private set; } private SimpleAction end_action; private SimpleAction kill_action; @@ -17,18 +15,8 @@ public class Monitor.ProcessView : Granite.Bin { construct { treeview_model = new TreeViewModel (); - var filter_model = new Gtk.TreeModelFilter (treeview_model, null); - filter_model.set_visible_func (filter_func); - - var sort_model = new Gtk.TreeModelSort.with_model (filter_model); - - process_tree_view = new CPUProcessTreeView (treeview_model); - process_tree_view.process_selected.connect ((process) => on_process_selected (process)); - process_tree_view.set_model (sort_model); - - var process_tree_view_scrolled = new Gtk.ScrolledWindow () { - child = process_tree_view - }; + process_tree_view = new ProcessTreeView (treeview_model); + treeview_model.process_selected.connect ((process) => on_process_selected (process)); process_info_view = new ProcessInfoView () { // This might be useless since first process is selected @@ -38,8 +26,10 @@ public class Monitor.ProcessView : Granite.Bin { visible = false, }; + treeview_model.process_manager.updated.connect (process_info_view.update); + var paned = new Gtk.Paned (HORIZONTAL) { - start_child = process_tree_view_scrolled, + start_child = process_tree_view, end_child = process_info_view, shrink_end_child = false, resize_end_child = false, @@ -49,8 +39,6 @@ public class Monitor.ProcessView : Granite.Bin { child = paned; - notify["needle"].connect (filter_model.refilter); - kill_action = new SimpleAction ("kill", null); kill_action.activate.connect (action_kill); @@ -144,54 +132,4 @@ public class Monitor.ProcessView : Granite.Bin { kill_action.set_enabled (process.uid == Posix.getuid ()); } - public void update () { - new Thread ("update-processes", () => { - Idle.add (() => { - process_info_view.update (); - treeview_model.process_manager.update_processes.begin (); - - return false; - }); - return true; - }); - - } - - private bool filter_func (Gtk.TreeModel model, Gtk.TreeIter iter) { - string name_haystack; - int pid_haystack; - string cmd_haystack; - bool found = false; - - if (needle.length == 0) { - return true; - } - - model.get (iter, Column.NAME, out name_haystack, -1); - model.get (iter, Column.PID, out pid_haystack, -1); - model.get (iter, Column.CMD, out cmd_haystack, -1); - - // sometimes name_haystack is null - if (name_haystack != null) { - bool name_found = name_haystack.casefold ().contains (needle.casefold ()) || false; - bool pid_found = pid_haystack.to_string ().casefold ().contains (needle.casefold ()) || false; - bool cmd_found = cmd_haystack.casefold ().contains (needle.casefold ()) || false; - found = name_found || pid_found || cmd_found; - } - - Gtk.TreeIter child_iter; - bool child_found = false; - - if (model.iter_children (out child_iter, iter)) { - do { - child_found = filter_func (model, child_iter); - } while (model.iter_next (ref child_iter) && !child_found); - } - - if (child_found && needle.length > 0) { - process_tree_view.expand_all (); - } - - return found || child_found; - } } diff --git a/src/meson.build b/src/meson.build index ffd4d65ef..036d04339 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,7 +7,8 @@ source_app_files = [ # Views 'Views/ProcessView/ProcessView.vala', 'Views/ProcessView/ProcessInfoView/ProcessInfoView.vala', - 'Views/ProcessView/ProcessTreeView/CPUProcessTreeView.vala', + 'Views/ProcessView/ProcessTreeView/ProcessTreeView.vala', + 'Views/ProcessView/ProcessTreeView/ProcessTreeViewNameCell.vala', 'Views/PreferencesView.vala', @@ -33,6 +34,8 @@ source_app_files = [ # Models 'Models/TreeViewModel.vala', + 'Models/ProcessRowData.vala', + 'Models/TreeViewFilter.vala', 'Models/OpenFile.vala', # Other