#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(dead_code)]
#![allow(clippy::needless_range_loop)]
use std::ffi::CStr;
use std::os::unix::fs::DirBuilderExt;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::ported::crt::ColorScheme;
use crate::ported::dynamiccolumn::{DynamicColumn_lookup, DynamicColumn_search};
use crate::ported::hashtable::Hashtable;
use crate::ported::linux::linuxprocess::{Process_fields, LAST_PROCESSFIELD};
use crate::ported::dynamicscreen::DynamicScreen;
use crate::ported::machine::{Machine, TableHandle};
use crate::ported::meter::{BAR_METERMODE, TEXT_METERMODE};
use crate::ported::process::{ProcessField, DEFAULT_HIGHLIGHT_SECS};
use crate::ported::xutils::{String_eq, String_split, String_startsWith, String_trim};
const DEFAULT_DELAY: i32 = 15;
const CONFIG_READER_MIN_VERSION: i32 = 3;
const CONFIGDIR: &str = "/.config";
const SYSCONFDIR: &str = "/etc";
const PID: RowField = ProcessField::PID as RowField;
pub type MeterModeId = u32;
#[repr(i32)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum HeaderLayout {
HF_INVALID = -1,
HF_ONE_100 = 0,
#[default]
HF_TWO_50_50,
HF_TWO_33_67,
HF_TWO_67_33,
HF_THREE_33_34_33,
HF_THREE_25_25_50,
HF_THREE_25_50_25,
HF_THREE_50_25_25,
HF_THREE_40_30_30,
HF_THREE_30_40_30,
HF_THREE_30_30_40,
HF_THREE_40_20_40,
HF_FOUR_25_25_25_25,
LAST_HEADER_LAYOUT,
}
pub fn HeaderLayout_getColumns(hLayout: HeaderLayout) -> usize {
use HeaderLayout::*;
match hLayout {
HF_ONE_100 => 1,
HF_TWO_50_50 | HF_TWO_33_67 | HF_TWO_67_33 => 2,
HF_THREE_33_34_33 | HF_THREE_25_25_50 | HF_THREE_25_50_25 | HF_THREE_50_25_25
| HF_THREE_40_30_30 | HF_THREE_30_40_30 | HF_THREE_30_30_40 | HF_THREE_40_20_40 => 3,
HF_FOUR_25_25_25_25 => 4,
HF_INVALID | LAST_HEADER_LAYOUT => {
panic!("HeaderLayout_getColumns: uninitialized layout {hLayout:?}")
}
}
}
const HEADER_LAYOUTS_IN_ORDER: [HeaderLayout; HeaderLayout::LAST_HEADER_LAYOUT as usize] = {
use HeaderLayout::*;
[
HF_ONE_100,
HF_TWO_50_50,
HF_TWO_33_67,
HF_TWO_67_33,
HF_THREE_33_34_33,
HF_THREE_25_25_50,
HF_THREE_25_50_25,
HF_THREE_50_25_25,
HF_THREE_40_30_30,
HF_THREE_30_40_30,
HF_THREE_30_30_40,
HF_THREE_40_20_40,
HF_FOUR_25_25_25_25,
]
};
pub fn HeaderLayout_getName(hLayout: HeaderLayout) -> &'static str {
HeaderLayout_layouts[hLayout as usize].name
}
pub fn HeaderLayout_fromName(name: &str) -> HeaderLayout {
for i in 0..HeaderLayout::LAST_HEADER_LAYOUT as usize {
if String_eq(HeaderLayout_layouts[i].name, name) {
return HEADER_LAYOUTS_IN_ORDER[i];
}
}
HeaderLayout::LAST_HEADER_LAYOUT
}
pub struct HeaderLayoutDef {
pub columns: u8,
pub widths: [u8; 4],
pub name: &'static str,
pub description: &'static str,
}
#[allow(non_upper_case_globals)] pub static HeaderLayout_layouts: [HeaderLayoutDef; HeaderLayout::LAST_HEADER_LAYOUT as usize] = [
HeaderLayoutDef {
columns: 1,
widths: [100, 0, 0, 0],
name: "one_100",
description: "1 column - full width",
},
HeaderLayoutDef {
columns: 2,
widths: [50, 50, 0, 0],
name: "two_50_50",
description: "2 columns - 50/50 (default)",
},
HeaderLayoutDef {
columns: 2,
widths: [33, 67, 0, 0],
name: "two_33_67",
description: "2 columns - 33/67",
},
HeaderLayoutDef {
columns: 2,
widths: [67, 33, 0, 0],
name: "two_67_33",
description: "2 columns - 67/33",
},
HeaderLayoutDef {
columns: 3,
widths: [33, 34, 33, 0],
name: "three_33_34_33",
description: "3 columns - 33/34/33",
},
HeaderLayoutDef {
columns: 3,
widths: [25, 25, 50, 0],
name: "three_25_25_50",
description: "3 columns - 25/25/50",
},
HeaderLayoutDef {
columns: 3,
widths: [25, 50, 25, 0],
name: "three_25_50_25",
description: "3 columns - 25/50/25",
},
HeaderLayoutDef {
columns: 3,
widths: [50, 25, 25, 0],
name: "three_50_25_25",
description: "3 columns - 50/25/25",
},
HeaderLayoutDef {
columns: 3,
widths: [40, 30, 30, 0],
name: "three_40_30_30",
description: "3 columns - 40/30/30",
},
HeaderLayoutDef {
columns: 3,
widths: [30, 40, 30, 0],
name: "three_30_40_30",
description: "3 columns - 30/40/30",
},
HeaderLayoutDef {
columns: 3,
widths: [30, 30, 40, 0],
name: "three_30_30_40",
description: "3 columns - 30/30/40",
},
HeaderLayoutDef {
columns: 3,
widths: [40, 20, 40, 0],
name: "three_40_20_40",
description: "3 columns - 40/20/40",
},
HeaderLayoutDef {
columns: 4,
widths: [25, 25, 25, 25],
name: "four_25_25_25_25",
description: "4 columns - 25/25/25/25",
},
];
pub struct ScreenDefaults<'a> {
pub name: Option<&'a str>,
pub columns: Option<&'a str>,
pub sortKey: Option<&'a str>,
pub treeSortKey: Option<&'a str>,
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct MeterColumnSetting {
pub len: usize,
pub names: Option<Vec<String>>,
pub modes: Option<Vec<MeterModeId>>,
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct Settings {
pub filename: Option<String>,
pub initialFilename: Option<String>,
pub dynamicColumns: Option<*mut Hashtable>,
pub dynamicMeters: Option<*mut Hashtable>,
pub dynamicScreens: Option<*mut Hashtable>,
pub hLayout: HeaderLayout,
pub hColumns: Vec<MeterColumnSetting>,
pub screens: Vec<ScreenSettings>,
pub ssIndex: u32,
pub writeConfig: bool,
pub config_version: i32,
pub colorScheme: i32,
pub delay: i32,
pub countCPUsFromOne: bool,
pub detailedCPUTime: bool,
pub showCPUUsage: bool,
pub showCPUFrequency: bool,
pub showCPUSMTLabels: bool,
pub showCPUTemperature: bool,
pub degreeFahrenheit: bool,
pub showProgramPath: bool,
pub shadowOtherUsers: bool,
pub showThreadNames: bool,
pub hideKernelThreads: bool,
pub hideRunningInContainer: bool,
pub hideUserlandThreads: bool,
pub highlightBaseName: bool,
pub highlightDeletedExe: bool,
pub shadowDistPathPrefix: bool,
pub highlightMegabytes: bool,
pub highlightThreads: bool,
pub highlightChanges: bool,
pub highlightDelaySecs: i32,
pub findCommInCmdline: bool,
pub stripExeFromCmdline: bool,
pub showMergedCommand: bool,
pub updateProcessNames: bool,
pub accountGuestInCPUMeter: bool,
pub headerMargin: bool,
pub screenTabs: bool,
pub showCachedMemory: bool,
pub enableMouse: bool,
pub hideFunctionBar: i32,
pub topologyAffinity: bool,
pub changed: bool,
pub lastUpdate: u64,
}
fn atoi(s: &str) -> i32 {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
let mut sign: i32 = 1;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
if bytes[i] == b'-' {
sign = -1;
}
i += 1;
}
let mut n: i32 = 0;
while i < bytes.len() && bytes[i].is_ascii_digit() {
n = n.wrapping_mul(10).wrapping_add((bytes[i] - b'0') as i32);
i += 1;
}
sign.wrapping_mul(n)
}
pub fn Settings_splitLineToIDs(line: &str) -> Vec<String> {
let trim = String_trim(line);
String_split(&trim, ' ')
}
pub fn Settings_readMeters(this: &mut Settings, line: &str, column: usize) {
let column = column.min(HeaderLayout_getColumns(this.hLayout) - 1);
this.hColumns[column].names = Some(Settings_splitLineToIDs(line));
}
pub fn Settings_readMeterModes(this: &mut Settings, line: &str, column: usize) {
let ids = Settings_splitLineToIDs(line);
let len = ids.len();
let column = column.min(HeaderLayout_getColumns(this.hLayout) - 1);
this.hColumns[column].len = len;
let modes = if len != 0 {
Some(ids.iter().map(|id| atoi(id) as MeterModeId).collect())
} else {
None
};
this.hColumns[column].modes = modes;
}
pub fn Settings_validateMeters(this: &Settings) -> bool {
let colCount = HeaderLayout_getColumns(this.hLayout);
let mut anyMeter = false;
for column in 0..colCount {
let names = &this.hColumns[column].names;
let modes = &this.hColumns[column].modes;
let len = this.hColumns[column].len;
if len == 0 {
continue;
}
if names.is_none() || modes.is_none() {
return false;
}
anyMeter |= len != 0;
let names = names.as_ref().unwrap();
for meterIdx in 0..len {
if meterIdx >= names.len() {
return false;
}
}
if names.len() > len {
return false;
}
}
anyMeter
}
pub fn Settings_deleteColumns(this: &mut Settings) {
this.hColumns.clear();
}
pub fn Settings_deleteScreens(this: &mut Settings) {
this.screens.clear();
}
pub fn Settings_delete(mut this: Settings) {
Settings_deleteColumns(&mut this);
Settings_deleteScreens(&mut this);
}
pub fn Settings_defaultMeters(this: &mut Settings, host: &Machine) {
let initialCpuCount = host.activeCPUs;
let mut sizes: [usize; 2] = [3, 3];
if initialCpuCount > 4 && initialCpuCount <= 128 {
sizes[1] += 1;
}
this.hLayout = HeaderLayout::HF_TWO_50_50;
let mut names0: Vec<String> = Vec::new();
let mut modes0: Vec<MeterModeId> = Vec::new();
let mut names1: Vec<String> = Vec::new();
let mut modes1: Vec<MeterModeId> = Vec::new();
if initialCpuCount > 128 {
names0.push("CPU".to_string());
modes0.push(BAR_METERMODE);
} else if initialCpuCount > 32 {
names0.push("LeftCPUs8".to_string());
modes0.push(BAR_METERMODE);
names1.push("RightCPUs8".to_string());
modes1.push(BAR_METERMODE);
} else if initialCpuCount > 16 {
names0.push("LeftCPUs4".to_string());
modes0.push(BAR_METERMODE);
names1.push("RightCPUs4".to_string());
modes1.push(BAR_METERMODE);
} else if initialCpuCount > 8 {
names0.push("LeftCPUs2".to_string());
modes0.push(BAR_METERMODE);
names1.push("RightCPUs2".to_string());
modes1.push(BAR_METERMODE);
} else if initialCpuCount > 4 {
names0.push("LeftCPUs".to_string());
modes0.push(BAR_METERMODE);
names1.push("RightCPUs".to_string());
modes1.push(BAR_METERMODE);
} else {
names0.push("AllCPUs".to_string());
modes0.push(BAR_METERMODE);
}
names0.push("Memory".to_string());
modes0.push(BAR_METERMODE);
names0.push("Swap".to_string());
modes0.push(BAR_METERMODE);
names1.push("Tasks".to_string());
modes1.push(TEXT_METERMODE);
names1.push("LoadAverage".to_string());
modes1.push(TEXT_METERMODE);
names1.push("Uptime".to_string());
modes1.push(TEXT_METERMODE);
this.hColumns = vec![
MeterColumnSetting {
len: sizes[0],
names: Some(names0),
modes: Some(modes0),
},
MeterColumnSetting {
len: sizes[1],
names: Some(names1),
modes: Some(modes1),
},
];
}
pub fn toFieldName<'a>(
columns: &'a Hashtable,
id: i32,
enabled: Option<&mut bool>,
) -> Option<&'a str> {
if id < 0 {
if let Some(e) = enabled {
*e = false;
}
return None;
}
if id as usize >= LAST_PROCESSFIELD {
let column = DynamicColumn_lookup(columns, id as u32);
if let Some(e) = enabled {
*e = column.is_some_and(|c| c.enabled);
}
return column.map(|c| c.name.as_str());
}
if let Some(e) = enabled {
*e = true;
}
Some(Process_fields[id as usize].name)
}
pub fn toFieldIndex(columns: &Hashtable, str_: &str) -> i32 {
if str_.as_bytes().first().is_some_and(u8::is_ascii_digit) {
let id = atoi(str_) + 1;
if toFieldName(columns, id, None).is_some() {
return id;
}
} else {
if let Some(after) = str_.strip_prefix("Dynamic(") {
let token: String = after
.chars()
.take_while(|c| !c.is_whitespace())
.take(30)
.collect();
if let Some(pos) = token.rfind(')') {
let name = &token[..pos];
let mut key: u32 = 0;
if DynamicColumn_search(Some(columns), name, Some(&mut key)).is_some() {
return key as i32;
}
}
}
for p in 1..LAST_PROCESSFIELD as i32 {
if let Some(p_name) = toFieldName(columns, p, None) {
if p_name == str_ {
return p;
}
}
}
}
-1
}
pub fn ScreenSettings_readFields(ss: &mut ScreenSettings, columns: &Hashtable, line: &str) {
let trim = String_trim(line);
let ids = String_split(&trim, ' ');
ss.fields.clear();
for id_str in &ids {
let id = toFieldIndex(columns, id_str);
if id >= 0 {
ss.fields.push(id);
}
if id > 0 && (id as usize) < LAST_PROCESSFIELD {
ss.flags |= Process_fields[id as usize].flags;
}
}
}
pub fn Settings_initScreenSettings(
this: &mut Settings,
mut ss: ScreenSettings,
columns: &str,
) -> usize {
match this.dynamicColumns {
Some(p) => ScreenSettings_readFields(&mut ss, unsafe { &*p }, columns),
None => ss.fields.clear(),
}
this.screens.push(ss);
this.screens.len() - 1
}
pub fn Settings_newScreen(this: &mut Settings, defaults: &ScreenDefaults) -> usize {
let resolve = |name: &str| -> RowField {
match this.dynamicColumns {
Some(p) => toFieldIndex(unsafe { &*p }, name),
None => -1,
}
};
let sortKey = match defaults.sortKey {
Some(sk) => resolve(sk),
None => PID,
};
let treeSortKey = match defaults.treeSortKey {
Some(tsk) => resolve(tsk),
None => PID,
};
let sortDesc = if sortKey >= 0 && (sortKey as usize) < LAST_PROCESSFIELD {
Process_fields[sortKey as usize].defaultSortDesc
} else {
true };
let ss = ScreenSettings {
heading: defaults.name.map(|s| s.to_string()),
dynamic: None,
table: None,
fields: Vec::new(),
flags: 0,
direction: if sortDesc { -1 } else { 1 },
treeDirection: 1,
sortKey,
treeSortKey,
treeView: false,
treeViewAlwaysByPID: false,
allBranchesCollapsed: false,
..Default::default()
};
Settings_initScreenSettings(this, ss, defaults.columns.unwrap_or(""))
}
pub fn Settings_newDynamicScreen(
this: &mut Settings,
tab: &str,
screen: &DynamicScreen,
table: Option<TableHandle>,
) -> usize {
let column_keys = screen.columnKeys.as_deref().unwrap_or("");
let sortKey = match this.dynamicColumns {
Some(p) => toFieldIndex(unsafe { &*p }, column_keys),
None => -1,
};
let ss = ScreenSettings {
heading: Some(tab.to_string()),
dynamic: Some(screen.name.clone()),
table,
fields: Vec::new(),
direction: screen.direction,
treeDirection: 1,
sortKey,
..Default::default()
};
Settings_initScreenSettings(this, ss, column_keys)
}
pub fn ScreenSettings_delete(this: ScreenSettings) {
let _ = this;
}
pub fn Settings_defaultScreens(this: &mut Settings) -> usize {
if !this.screens.is_empty() {
return 0;
}
Settings_newScreen(
this,
&ScreenDefaults {
name: Some("Main"),
columns: Some(
"PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
),
sortKey: Some("PERCENT_CPU"),
treeSortKey: None,
},
);
0
}
pub fn Settings_read(
this: &mut Settings,
fileName: &str,
host: &Machine,
checkWritability: bool,
) -> bool {
use std::fs::OpenOptions;
use std::io::{ErrorKind, Read};
use std::os::unix::fs::{MetadataExt, OpenOptionsExt, PermissionsExt};
let mut file: Option<std::fs::File> = None;
if checkWritability {
match OpenOptions::new()
.read(true)
.write(true)
.custom_flags(libc::O_NOCTTY | libc::O_NOFOLLOW)
.open(fileName)
{
Ok(f) => {
this.writeConfig = match f.metadata() {
Ok(sb) => {
sb.file_type().is_file()
&& (sb.permissions().mode() & (libc::S_IWUSR as u32)) != 0
&& sb.uid() == unsafe { libc::geteuid() }
}
Err(_) => false,
};
file = Some(f);
}
Err(e) => {
this.writeConfig = e.kind() == ErrorKind::NotFound;
match e.raw_os_error() {
Some(code)
if code == libc::EACCES
|| code == libc::EPERM
|| code == libc::EROFS => {}
_ => return false,
}
}
}
}
if file.is_none() {
match OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOCTTY)
.open(fileName)
{
Ok(f) => file = Some(f),
Err(_) => return false,
}
}
let mut fp = match file {
Some(f) => f,
None => return false,
};
let mut contents = String::new();
if fp.read_to_string(&mut contents).is_err() {
return false;
}
let mut screen: Option<usize> = None;
let mut didReadMeters = false;
let mut didReadAny = false;
for line in contents.lines() {
didReadAny = true;
let option = String_split(line, '=');
if option.len() < 2 {
continue;
}
let key = option[0].as_str();
let val = option[1].as_str();
if String_eq(key, "config_reader_min_version") {
this.config_version = atoi(val);
if this.config_version > CONFIG_READER_MIN_VERSION {
eprintln!("WARNING: {fileName} specifies configuration format");
eprintln!(
" version v{}, but this htop binary only supports up to version v{}.",
this.config_version, CONFIG_READER_MIN_VERSION
);
eprintln!(
" The configuration file will be downgraded to v{CONFIG_READER_MIN_VERSION} when htop exits."
);
return false;
}
} else if String_eq(key, "fields") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
if let Some(p) = this.dynamicColumns {
ScreenSettings_readFields(&mut this.screens[idx], unsafe { &*p }, val);
}
} else if String_eq(key, "sort_key") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
this.screens[idx].sortKey = atoi(val) + 1;
} else if String_eq(key, "tree_sort_key") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
this.screens[idx].treeSortKey = atoi(val) + 1;
} else if String_eq(key, "sort_direction") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
this.screens[idx].direction = atoi(val);
} else if String_eq(key, "tree_sort_direction") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
this.screens[idx].treeDirection = atoi(val);
} else if String_eq(key, "tree_view") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
this.screens[idx].treeView = atoi(val) != 0;
} else if String_eq(key, "tree_view_always_by_pid") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
this.screens[idx].treeViewAlwaysByPID = atoi(val) != 0;
} else if String_eq(key, "all_branches_collapsed") && this.config_version <= 2 {
let idx = Settings_defaultScreens(this);
screen = Some(idx);
this.screens[idx].allBranchesCollapsed = atoi(val) != 0;
} else if String_eq(key, "hide_kernel_threads") {
this.hideKernelThreads = atoi(val) != 0;
} else if String_eq(key, "hide_userland_threads") {
this.hideUserlandThreads = atoi(val) != 0;
} else if String_eq(key, "hide_running_in_container") {
this.hideRunningInContainer = atoi(val) != 0;
} else if String_eq(key, "shadow_other_users") {
this.shadowOtherUsers = atoi(val) != 0;
} else if String_eq(key, "show_thread_names") {
this.showThreadNames = atoi(val) != 0;
} else if String_eq(key, "show_program_path") {
this.showProgramPath = atoi(val) != 0;
} else if String_eq(key, "highlight_base_name") {
this.highlightBaseName = atoi(val) != 0;
} else if String_eq(key, "highlight_deleted_exe") {
this.highlightDeletedExe = atoi(val) != 0;
} else if String_eq(key, "shadow_distribution_path_prefix") {
this.shadowDistPathPrefix = atoi(val) != 0;
} else if String_eq(key, "highlight_megabytes") {
this.highlightMegabytes = atoi(val) != 0;
} else if String_eq(key, "highlight_threads") {
this.highlightThreads = atoi(val) != 0;
} else if String_eq(key, "highlight_changes") {
this.highlightChanges = atoi(val) != 0;
} else if String_eq(key, "highlight_changes_delay_secs") {
this.highlightDelaySecs = atoi(val).clamp(1, 24 * 60 * 60);
} else if String_eq(key, "find_comm_in_cmdline") {
this.findCommInCmdline = atoi(val) != 0;
} else if String_eq(key, "strip_exe_from_cmdline") {
this.stripExeFromCmdline = atoi(val) != 0;
} else if String_eq(key, "show_merged_command") {
this.showMergedCommand = atoi(val) != 0;
} else if String_eq(key, "header_margin") {
this.headerMargin = atoi(val) != 0;
} else if String_eq(key, "screen_tabs") {
this.screenTabs = atoi(val) != 0;
} else if String_eq(key, "expand_system_time") {
this.detailedCPUTime = atoi(val) != 0;
} else if String_eq(key, "detailed_cpu_time") {
this.detailedCPUTime = atoi(val) != 0;
} else if String_eq(key, "cpu_count_from_one") {
this.countCPUsFromOne = atoi(val) != 0;
} else if String_eq(key, "cpu_count_from_zero") {
this.countCPUsFromOne = atoi(val) == 0;
} else if String_eq(key, "show_cpu_smt_labels") {
this.showCPUSMTLabels = atoi(val) != 0;
} else if String_eq(key, "show_cpu_usage") {
this.showCPUUsage = atoi(val) != 0;
} else if String_eq(key, "show_cpu_frequency") {
this.showCPUFrequency = atoi(val) != 0;
} else if String_eq(key, "show_cached_memory") {
this.showCachedMemory = atoi(val) != 0;
} else if String_eq(key, "show_cpu_temperature") {
this.showCPUTemperature = atoi(val) != 0;
} else if String_eq(key, "degree_fahrenheit") {
this.degreeFahrenheit = atoi(val) != 0;
} else if String_eq(key, "update_process_names") {
this.updateProcessNames = atoi(val) != 0;
} else if String_eq(key, "account_guest_in_cpu_meter") {
this.accountGuestInCPUMeter = atoi(val) != 0;
} else if String_eq(key, "delay") {
this.delay = atoi(val).clamp(1, 255);
} else if String_eq(key, "color_scheme") {
this.colorScheme = atoi(val);
if this.colorScheme < 0
|| this.colorScheme >= ColorScheme::LAST_COLORSCHEME as i32
{
this.colorScheme = 0;
}
} else if String_eq(key, "enable_mouse") {
this.enableMouse = atoi(val) != 0;
} else if String_eq(key, "header_layout") {
let layout = if val.as_bytes().first().is_some_and(u8::is_ascii_digit) {
let n = atoi(val);
if n >= 0 && (n as usize) < HeaderLayout::LAST_HEADER_LAYOUT as usize {
HEADER_LAYOUTS_IN_ORDER[n as usize]
} else {
HeaderLayout::HF_INVALID
}
} else {
HeaderLayout_fromName(val)
};
this.hLayout = if (layout as i32) < 0
|| (layout as i32) >= HeaderLayout::LAST_HEADER_LAYOUT as i32
{
HeaderLayout::HF_TWO_50_50
} else {
layout
};
this.hColumns =
vec![MeterColumnSetting::default(); HeaderLayout_getColumns(this.hLayout)];
} else if String_eq(key, "left_meters") {
Settings_readMeters(this, val, 0);
didReadMeters = true;
} else if String_eq(key, "right_meters") {
Settings_readMeters(this, val, 1);
didReadMeters = true;
} else if String_eq(key, "left_meter_modes") {
Settings_readMeterModes(this, val, 0);
didReadMeters = true;
} else if String_eq(key, "right_meter_modes") {
Settings_readMeterModes(this, val, 1);
didReadMeters = true;
} else if String_startsWith(key, "column_meters_") {
let col = atoi(&key["column_meters_".len()..]) as u32 as usize;
Settings_readMeters(this, val, col);
didReadMeters = true;
} else if String_startsWith(key, "column_meter_modes_") {
let col = atoi(&key["column_meter_modes_".len()..]) as u32 as usize;
Settings_readMeterModes(this, val, col);
didReadMeters = true;
} else if String_eq(key, "hide_function_bar") {
this.hideFunctionBar = atoi(val);
} else if String_startsWith(key, "screen:") {
let name = &key[7..];
let idx = Settings_newScreen(
this,
&ScreenDefaults {
name: Some(name),
columns: Some(val),
sortKey: None,
treeSortKey: None,
},
);
screen = Some(idx);
} else if String_eq(key, ".sort_key") {
if let Some(idx) = screen {
let k = match this.dynamicColumns {
Some(p) => toFieldIndex(unsafe { &*p }, val),
None => -1,
};
this.screens[idx].sortKey = if k > 0 { k } else { PID };
}
} else if String_eq(key, ".tree_sort_key") {
if let Some(idx) = screen {
let k = match this.dynamicColumns {
Some(p) => toFieldIndex(unsafe { &*p }, val),
None => -1,
};
this.screens[idx].treeSortKey = if k > 0 { k } else { PID };
}
} else if String_eq(key, ".sort_direction") {
if let Some(idx) = screen {
this.screens[idx].direction = atoi(val);
}
} else if String_eq(key, ".tree_sort_direction") {
if let Some(idx) = screen {
this.screens[idx].treeDirection = atoi(val);
}
} else if String_eq(key, ".tree_view") {
if let Some(idx) = screen {
this.screens[idx].treeView = atoi(val) != 0;
}
} else if String_eq(key, ".tree_view_always_by_pid") {
if let Some(idx) = screen {
this.screens[idx].treeViewAlwaysByPID = atoi(val) != 0;
}
} else if String_eq(key, ".all_branches_collapsed") {
if let Some(idx) = screen {
this.screens[idx].allBranchesCollapsed = atoi(val) != 0;
}
} else if String_eq(key, ".dynamic") {
if let Some(idx) = screen {
this.screens[idx].dynamic = Some(val.to_string());
}
}
}
if !didReadMeters || !Settings_validateMeters(this) {
Settings_defaultMeters(this, host);
}
if this.screens.is_empty() {
Settings_defaultScreens(this);
}
didReadAny
}
pub fn writeFields(
out: &mut String,
fields: &[RowField],
columns: &Hashtable,
byName: bool,
separator: char,
) {
let mut sep = "";
for &field in fields {
if field < LAST_PROCESSFIELD as i32 && byName {
let pName = toFieldName(columns, field, None);
out.push_str(sep);
out.push_str(pName.unwrap_or(""));
} else if field >= LAST_PROCESSFIELD as i32 && byName {
let mut enabled = false;
let pName = toFieldName(columns, field, Some(&mut enabled));
if enabled {
out.push_str(sep);
out.push_str("Dynamic(");
out.push_str(pName.unwrap_or(""));
out.push(')');
}
} else {
out.push_str(sep);
out.push_str(&(field - 1).to_string());
}
sep = " ";
}
out.push(separator);
}
pub fn writeList(out: &mut String, list: &[String], len: usize, separator: char) {
let mut sep = "";
for i in 0..len {
out.push_str(sep);
out.push_str(&list[i]);
sep = " ";
}
out.push(separator);
}
pub fn writeMeters(this: &Settings, out: &mut String, separator: char, column: usize) {
let col = &this.hColumns[column];
if col.len != 0 {
writeList(
out,
col.names.as_ref().expect("names set when len != 0"),
col.len,
separator,
);
} else {
out.push('!');
out.push(separator);
}
}
pub fn writeMeterModes(this: &Settings, out: &mut String, separator: char, column: usize) {
let col = &this.hColumns[column];
if col.len != 0 {
let modes = col.modes.as_ref().expect("modes set when len != 0");
let mut sep = "";
for i in 0..col.len {
out.push_str(sep);
out.push_str(&modes[i].to_string());
sep = " ";
}
} else {
out.push('!');
}
out.push(separator);
}
fn full_write(fd: libc::c_int, mut buf: &[u8]) -> libc::ssize_t {
let mut written: libc::ssize_t = 0;
while !buf.is_empty() {
let r = unsafe { libc::write(fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
if r < 0 {
if std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
continue;
}
return r;
}
if r == 0 {
break;
}
written += r;
buf = &buf[r as usize..];
}
written
}
pub fn signal_safe_fprintf(fd: libc::c_int, s: &str) -> i32 {
let ret = full_write(fd, s.as_bytes());
ret.min(i32::MAX as libc::ssize_t) as i32
}
fn printSettingInteger(out: &mut String, separator: char, setting: &str, value: i32) {
out.push_str(setting);
out.push('=');
out.push_str(&value.to_string());
out.push(separator);
}
fn printSettingString(out: &mut String, separator: char, setting: &str, value: &str) {
out.push_str(setting);
out.push('=');
out.push_str(value);
out.push(separator);
}
pub fn Settings_write(this: &Settings, onCrash: bool) -> i32 {
let separator: char;
let mut tmpFilename: Option<String> = None;
let mut fdtmp: libc::c_int = -1;
if onCrash {
separator = ';';
} else if !this.writeConfig {
return 0;
} else {
let cur_umask = unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) };
let template = format!("{}.tmp.XXXXXX", this.filename.as_deref().unwrap_or(""));
let mut tmpl: Vec<u8> = template.into_bytes();
tmpl.push(0);
fdtmp = unsafe { libc::mkstemp(tmpl.as_mut_ptr() as *mut libc::c_char) };
unsafe { libc::umask(cur_umask) };
if fdtmp == -1 {
return -std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
}
let name = CStr::from_bytes_with_nul(&tmpl[..])
.ok()
.and_then(|c| c.to_str().ok())
.map(str::to_string);
tmpFilename = name;
separator = '\n';
}
let columns: &Hashtable = unsafe {
&*this
.dynamicColumns
.expect("Settings.dynamicColumns must be set (owned by Machine)")
};
let mut buf = String::new();
if !onCrash {
buf.push_str(
"# Beware! This file is rewritten by htop when settings are changed in the interface.\n",
);
buf.push_str("# The parser is also very primitive, and not human-friendly.\n");
}
printSettingString(
&mut buf,
separator,
"htop_version",
env!("CARGO_PKG_VERSION"),
);
printSettingInteger(
&mut buf,
separator,
"config_reader_min_version",
CONFIG_READER_MIN_VERSION,
);
buf.push_str("fields=");
writeFields(&mut buf, &this.screens[0].fields, columns, false, separator);
printSettingInteger(
&mut buf,
separator,
"hide_kernel_threads",
this.hideKernelThreads as i32,
);
printSettingInteger(
&mut buf,
separator,
"hide_userland_threads",
this.hideUserlandThreads as i32,
);
printSettingInteger(
&mut buf,
separator,
"hide_running_in_container",
this.hideRunningInContainer as i32,
);
printSettingInteger(
&mut buf,
separator,
"shadow_other_users",
this.shadowOtherUsers as i32,
);
printSettingInteger(
&mut buf,
separator,
"show_thread_names",
this.showThreadNames as i32,
);
printSettingInteger(
&mut buf,
separator,
"show_program_path",
this.showProgramPath as i32,
);
printSettingInteger(
&mut buf,
separator,
"highlight_base_name",
this.highlightBaseName as i32,
);
printSettingInteger(
&mut buf,
separator,
"highlight_deleted_exe",
this.highlightDeletedExe as i32,
);
printSettingInteger(
&mut buf,
separator,
"shadow_distribution_path_prefix",
this.shadowDistPathPrefix as i32,
);
printSettingInteger(
&mut buf,
separator,
"highlight_megabytes",
this.highlightMegabytes as i32,
);
printSettingInteger(
&mut buf,
separator,
"highlight_threads",
this.highlightThreads as i32,
);
printSettingInteger(
&mut buf,
separator,
"highlight_changes",
this.highlightChanges as i32,
);
printSettingInteger(
&mut buf,
separator,
"highlight_changes_delay_secs",
this.highlightDelaySecs,
);
printSettingInteger(
&mut buf,
separator,
"find_comm_in_cmdline",
this.findCommInCmdline as i32,
);
printSettingInteger(
&mut buf,
separator,
"strip_exe_from_cmdline",
this.stripExeFromCmdline as i32,
);
printSettingInteger(
&mut buf,
separator,
"show_merged_command",
this.showMergedCommand as i32,
);
printSettingInteger(
&mut buf,
separator,
"header_margin",
this.headerMargin as i32,
);
printSettingInteger(&mut buf, separator, "screen_tabs", this.screenTabs as i32);
printSettingInteger(
&mut buf,
separator,
"detailed_cpu_time",
this.detailedCPUTime as i32,
);
printSettingInteger(
&mut buf,
separator,
"cpu_count_from_one",
this.countCPUsFromOne as i32,
);
printSettingInteger(
&mut buf,
separator,
"show_cpu_smt_labels",
this.showCPUSMTLabels as i32,
);
printSettingInteger(
&mut buf,
separator,
"show_cpu_usage",
this.showCPUUsage as i32,
);
printSettingInteger(
&mut buf,
separator,
"show_cpu_frequency",
this.showCPUFrequency as i32,
);
printSettingInteger(
&mut buf,
separator,
"show_cached_memory",
this.showCachedMemory as i32,
);
printSettingInteger(
&mut buf,
separator,
"update_process_names",
this.updateProcessNames as i32,
);
printSettingInteger(
&mut buf,
separator,
"account_guest_in_cpu_meter",
this.accountGuestInCPUMeter as i32,
);
printSettingInteger(&mut buf, separator, "color_scheme", this.colorScheme);
printSettingInteger(&mut buf, separator, "enable_mouse", this.enableMouse as i32);
printSettingInteger(&mut buf, separator, "delay", this.delay);
printSettingInteger(
&mut buf,
separator,
"hide_function_bar",
this.hideFunctionBar,
);
printSettingString(
&mut buf,
separator,
"header_layout",
HeaderLayout_getName(this.hLayout),
);
for i in 0..HeaderLayout_getColumns(this.hLayout) {
buf.push_str(&format!("column_meters_{i}="));
writeMeters(this, &mut buf, separator, i);
buf.push_str(&format!("column_meter_modes_{i}="));
writeMeterModes(this, &mut buf, separator, i);
}
let s0 = &this.screens[0];
printSettingInteger(&mut buf, separator, "tree_view", s0.treeView as i32);
printSettingInteger(&mut buf, separator, "sort_key", s0.sortKey - 1);
printSettingInteger(&mut buf, separator, "tree_sort_key", s0.treeSortKey - 1);
printSettingInteger(&mut buf, separator, "sort_direction", s0.direction);
printSettingInteger(&mut buf, separator, "tree_sort_direction", s0.treeDirection);
printSettingInteger(
&mut buf,
separator,
"tree_view_always_by_pid",
s0.treeViewAlwaysByPID as i32,
);
printSettingInteger(
&mut buf,
separator,
"all_branches_collapsed",
s0.allBranchesCollapsed as i32,
);
for i in 0..this.screens.len() {
let ss = &this.screens[i];
let sortKey = toFieldName(columns, ss.sortKey, None).unwrap_or("");
let treeSortKey = toFieldName(columns, ss.treeSortKey, None).unwrap_or("");
buf.push_str(&format!("screen:{}=", ss.heading.as_deref().unwrap_or("")));
writeFields(&mut buf, &ss.fields, columns, true, separator);
if let Some(dynamic) = ss.dynamic.as_deref() {
printSettingString(&mut buf, separator, ".dynamic", dynamic);
if ss.sortKey != 0 && ss.sortKey != PID {
buf.push_str(&format!(".sort_key=Dynamic({sortKey}){separator}"));
}
if ss.treeSortKey != 0 && ss.treeSortKey != PID {
buf.push_str(&format!(".tree_sort_key=Dynamic({treeSortKey}){separator}"));
}
} else {
printSettingString(&mut buf, separator, ".sort_key", sortKey);
printSettingString(&mut buf, separator, ".tree_sort_key", treeSortKey);
printSettingInteger(
&mut buf,
separator,
".tree_view_always_by_pid",
ss.treeViewAlwaysByPID as i32,
);
}
printSettingInteger(&mut buf, separator, ".tree_view", ss.treeView as i32);
printSettingInteger(&mut buf, separator, ".sort_direction", ss.direction);
printSettingInteger(
&mut buf,
separator,
".tree_sort_direction",
ss.treeDirection,
);
printSettingInteger(
&mut buf,
separator,
".all_branches_collapsed",
ss.allBranchesCollapsed as i32,
);
}
if onCrash {
signal_safe_fprintf(libc::STDERR_FILENO, &buf);
return 0;
}
let mut r: i32 = 0;
let written = full_write(fdtmp, buf.as_bytes());
if written < 0 || (written as usize) != buf.len() {
let e = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
r = if e != 0 { -e } else { -libc::EBADF };
}
if unsafe { libc::close(fdtmp) } != 0 && r == 0 {
r = -std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
}
let tmp = tmpFilename.as_deref().unwrap_or("");
if r == 0 {
r = match std::fs::rename(tmp, this.filename.as_deref().unwrap_or("")) {
Ok(()) => 0,
Err(e) => -e.raw_os_error().unwrap_or(libc::EIO),
};
}
r
}
pub fn Settings_new(
host: &Machine,
dynamicMeters: Option<*mut Hashtable>,
dynamicColumns: Option<*mut Hashtable>,
dynamicScreens: Option<*mut Hashtable>,
) -> Settings {
let mut this = Settings {
writeConfig: true,
dynamicScreens,
dynamicColumns,
dynamicMeters,
hLayout: HeaderLayout::HF_TWO_50_50,
..Default::default()
};
this.hColumns = vec![MeterColumnSetting::default(); HeaderLayout_getColumns(this.hLayout)];
this.shadowOtherUsers = false;
this.showThreadNames = false;
this.hideKernelThreads = true;
this.hideUserlandThreads = false;
this.hideRunningInContainer = false;
this.highlightBaseName = false;
this.highlightDeletedExe = true;
this.shadowDistPathPrefix = false;
this.highlightMegabytes = true;
this.detailedCPUTime = false;
this.countCPUsFromOne = false;
this.showCPUSMTLabels = false;
this.showCPUUsage = true;
this.showCPUFrequency = false;
this.showCachedMemory = true;
this.updateProcessNames = false;
this.showProgramPath = true;
this.highlightThreads = true;
this.highlightChanges = false;
this.highlightDelaySecs = DEFAULT_HIGHLIGHT_SECS;
this.findCommInCmdline = true;
this.stripExeFromCmdline = true;
this.showMergedCommand = false;
this.hideFunctionBar = 0;
this.headerMargin = true;
this.screens = Vec::new();
let mut legacyDotfile: Option<String> = None;
if let Ok(rcfile) = std::env::var("HTOPRC") {
this.initialFilename = Some(rcfile);
} else {
let mut home = std::env::var("HOME").unwrap_or_default();
if home.is_empty() || !home.starts_with('/') {
home = unsafe {
let pw = libc::getpwuid(libc::getuid());
if !pw.is_null() && !(*pw).pw_dir.is_null() {
let dir = CStr::from_ptr((*pw).pw_dir).to_string_lossy().into_owned();
if dir.starts_with('/') {
dir
} else {
String::new()
}
} else {
String::new()
}
};
}
let xdg = std::env::var("XDG_CONFIG_HOME").unwrap_or_default();
let (initialFilename, configDir, htopDir);
if !xdg.is_empty() && xdg.starts_with('/') {
initialFilename = format!("{xdg}/htop/htoprc");
configDir = xdg.clone();
htopDir = format!("{xdg}/htop");
} else {
initialFilename = format!("{home}{CONFIGDIR}/htop/htoprc");
configDir = format!("{home}{CONFIGDIR}");
htopDir = format!("{home}{CONFIGDIR}/htop");
}
this.initialFilename = Some(initialFilename);
let _ = std::fs::DirBuilder::new().mode(0o700).create(&configDir);
let _ = std::fs::DirBuilder::new().mode(0o700).create(&htopDir);
legacyDotfile = Some(format!("{home}/.htoprc"));
}
let initial = this.initialFilename.clone().unwrap_or_default();
this.filename = match std::fs::canonicalize(&initial) {
Ok(p) => Some(p.to_string_lossy().into_owned()),
Err(_) => Some(initial),
};
this.colorScheme = 0;
this.enableMouse = true; this.changed = false;
this.delay = DEFAULT_DELAY;
let filename = this.filename.clone().unwrap_or_default();
let mut ok = Settings_read(&mut this, &filename, host, true);
if !ok {
if let Some(legacy) = legacyDotfile.clone() {
let writeConfig = this.writeConfig;
ok = Settings_read(&mut this, &legacy, host, writeConfig);
if ok && this.writeConfig {
if Settings_write(&this, false) == 0 {
let _ = std::fs::remove_file(&legacy);
}
}
}
}
if !ok {
this.screenTabs = true;
this.changed = true;
ok = Settings_read(
&mut this,
&format!("{SYSCONFDIR}/htoprc"),
host,
false,
);
}
if !ok {
Settings_defaultMeters(&mut this, host);
Settings_defaultScreens(&mut this);
}
this.ssIndex = 0;
this.lastUpdate = 1;
this
}
pub type RowField = i32;
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct ScreenSettings {
pub heading: Option<String>,
pub dynamic: Option<String>,
pub table: Option<TableHandle>,
pub fields: Vec<RowField>,
pub flags: u32,
pub direction: i32,
pub treeDirection: i32,
pub sortKey: RowField,
pub treeSortKey: RowField,
pub treeView: bool,
pub treeViewAlwaysByPID: bool,
pub allBranchesCollapsed: bool,
pub stableTreeView: i32,
}
pub fn ScreenSettings_getActiveSortKey(this: &ScreenSettings) -> RowField {
if this.treeView {
if this.treeViewAlwaysByPID {
1
} else {
this.treeSortKey
}
} else {
this.sortKey
}
}
pub fn ScreenSettings_getActiveDirection(this: &ScreenSettings) -> i32 {
if this.treeView {
this.treeDirection
} else {
this.direction
}
}
pub fn ScreenSettings_invertSortOrder(this: &mut ScreenSettings) {
let attr = if this.treeView {
&mut this.treeDirection
} else {
&mut this.direction
};
*attr = if *attr == 1 { -1 } else { 1 };
}
pub fn ScreenSettings_setSortKey(this: &mut ScreenSettings, sortKey: RowField) {
if this.treeViewAlwaysByPID || !this.treeView {
this.sortKey = sortKey;
this.direction = if Process_fields[sortKey as usize].defaultSortDesc {
-1
} else {
1
};
this.treeView = false;
} else {
this.treeSortKey = sortKey;
this.treeDirection = if Process_fields[sortKey as usize].defaultSortDesc {
-1
} else {
1
};
}
}
static READONLY: AtomicBool = AtomicBool::new(false);
pub fn Settings_enableReadonly() {
READONLY.store(true, Ordering::Relaxed);
}
pub fn Settings_isReadonly() -> bool {
READONLY.load(Ordering::Relaxed)
}
pub fn Settings_setHeaderLayout(this: &mut Settings, hLayout: HeaderLayout) {
let oldColumns = HeaderLayout_getColumns(this.hLayout);
let newColumns = HeaderLayout_getColumns(hLayout);
if newColumns > oldColumns {
this.hColumns
.resize_with(newColumns, MeterColumnSetting::default);
} else if newColumns < oldColumns {
this.hColumns.truncate(newColumns);
}
this.hLayout = hLayout;
this.changed = true;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_layouts_table_is_consistent() {
use HeaderLayout::*;
assert_eq!(HeaderLayout_layouts.len(), LAST_HEADER_LAYOUT as usize);
let all = [
HF_ONE_100,
HF_TWO_50_50,
HF_TWO_33_67,
HF_TWO_67_33,
HF_THREE_33_34_33,
HF_THREE_25_25_50,
HF_THREE_25_50_25,
HF_THREE_50_25_25,
HF_THREE_40_30_30,
HF_THREE_30_40_30,
HF_THREE_30_30_40,
HF_THREE_40_20_40,
HF_FOUR_25_25_25_25,
];
for layout in all {
let def = &HeaderLayout_layouts[layout as usize];
assert_eq!(
def.columns as usize,
HeaderLayout_getColumns(layout),
"{layout:?}"
);
let sum: u32 = def.widths[..def.columns as usize]
.iter()
.map(|&w| w as u32)
.sum();
assert_eq!(sum, 100, "{layout:?} widths sum");
assert!(def.widths[def.columns as usize..].iter().all(|&w| w == 0));
assert!(!def.name.is_empty() && !def.description.is_empty());
}
assert_eq!(
HeaderLayout_layouts[HF_TWO_67_33 as usize].widths,
[67, 33, 0, 0]
);
assert_eq!(HeaderLayout_layouts[HF_ONE_100 as usize].name, "one_100");
}
fn two_column_settings() -> Settings {
Settings {
hLayout: HeaderLayout::HF_TWO_50_50,
hColumns: vec![MeterColumnSetting::default(), MeterColumnSetting::default()],
screens: Vec::new(),
ssIndex: 0,
changed: false,
lastUpdate: 0,
..Default::default()
}
}
#[test]
fn split_line_to_ids_trims_then_splits_on_space() {
assert_eq!(
Settings_splitLineToIDs(" cpu mem swap "),
vec!["cpu", "mem", "swap"]
);
assert_eq!(Settings_splitLineToIDs("a b c"), vec!["a", "b", "", "c"]);
assert_eq!(Settings_splitLineToIDs("\tx y\n"), vec!["x", "y"]);
assert!(Settings_splitLineToIDs(" ").is_empty());
assert!(Settings_splitLineToIDs("").is_empty());
}
#[test]
fn read_meters_stores_names_and_clamps_column() {
let mut s = two_column_settings();
Settings_readMeters(&mut s, "AllCPUs Memory Swap", 0);
Settings_readMeters(&mut s, "Tasks LoadAverage Uptime", 1);
assert_eq!(
s.hColumns[0].names.as_deref().unwrap(),
["AllCPUs", "Memory", "Swap"]
);
assert_eq!(
s.hColumns[1].names.as_deref().unwrap(),
["Tasks", "LoadAverage", "Uptime"]
);
assert_eq!(s.hColumns[0].len, 0);
Settings_readMeters(&mut s, "OnlyOne", 5);
assert_eq!(s.hColumns[1].names.as_deref().unwrap(), ["OnlyOne"]);
}
#[test]
fn read_meter_modes_parses_ints_and_sets_len() {
let mut s = two_column_settings();
Settings_readMeterModes(&mut s, "1 1 1", 0);
assert_eq!(s.hColumns[0].len, 3);
assert_eq!(s.hColumns[0].modes.as_deref().unwrap(), [1u32, 1, 1]);
Settings_readMeterModes(&mut s, "2 x 4", 1);
assert_eq!(s.hColumns[1].len, 4);
assert_eq!(s.hColumns[1].modes.as_deref().unwrap(), [2u32, 0, 0, 4]);
Settings_readMeterModes(&mut s, "", 0);
assert_eq!(s.hColumns[0].len, 0);
assert!(s.hColumns[0].modes.is_none());
}
fn host_with_cpus(activeCPUs: u32) -> Machine {
Machine {
activeCPUs,
..Default::default()
}
}
#[test]
fn default_meters_small_cpu_uses_allcpus_three_and_three() {
let mut s = two_column_settings();
Settings_defaultMeters(&mut s, &host_with_cpus(4));
assert_eq!(s.hLayout, HeaderLayout::HF_TWO_50_50);
assert_eq!(s.hColumns.len(), 2);
assert_eq!(s.hColumns[0].len, 3);
assert_eq!(s.hColumns[1].len, 3);
assert_eq!(
s.hColumns[0].names.as_deref().unwrap(),
["AllCPUs", "Memory", "Swap"]
);
assert_eq!(
s.hColumns[0].modes.as_deref().unwrap(),
[BAR_METERMODE, BAR_METERMODE, BAR_METERMODE]
);
assert_eq!(
s.hColumns[1].names.as_deref().unwrap(),
["Tasks", "LoadAverage", "Uptime"]
);
assert_eq!(
s.hColumns[1].modes.as_deref().unwrap(),
[TEXT_METERMODE, TEXT_METERMODE, TEXT_METERMODE]
);
assert!(Settings_validateMeters(&s));
}
#[test]
fn default_meters_midrange_cpu_adds_right_cpus_split() {
let mut s = two_column_settings();
Settings_defaultMeters(&mut s, &host_with_cpus(6));
assert_eq!(s.hColumns[0].len, 3);
assert_eq!(s.hColumns[1].len, 4);
assert_eq!(
s.hColumns[0].names.as_deref().unwrap(),
["LeftCPUs", "Memory", "Swap"]
);
assert_eq!(
s.hColumns[1].names.as_deref().unwrap(),
["RightCPUs", "Tasks", "LoadAverage", "Uptime"]
);
assert_eq!(
s.hColumns[1].modes.as_deref().unwrap(),
[
BAR_METERMODE,
TEXT_METERMODE,
TEXT_METERMODE,
TEXT_METERMODE
]
);
assert!(Settings_validateMeters(&s));
}
#[test]
fn default_meters_cpu_bucket_names_track_thresholds() {
for (cpus, left, right) in [
(9u32, "LeftCPUs2", "RightCPUs2"),
(17, "LeftCPUs4", "RightCPUs4"),
(33, "LeftCPUs8", "RightCPUs8"),
] {
let mut s = two_column_settings();
Settings_defaultMeters(&mut s, &host_with_cpus(cpus));
assert_eq!(s.hColumns[0].names.as_deref().unwrap()[0], left);
assert_eq!(s.hColumns[1].names.as_deref().unwrap()[0], right);
assert_eq!(s.hColumns[1].len, 4);
}
}
#[test]
fn default_meters_huge_cpu_shows_single_averaged_cpu() {
let mut s = two_column_settings();
Settings_defaultMeters(&mut s, &host_with_cpus(256));
assert_eq!(s.hColumns[0].len, 3);
assert_eq!(s.hColumns[1].len, 3);
assert_eq!(s.hColumns[0].names.as_deref().unwrap()[0], "CPU");
assert_eq!(
s.hColumns[1].names.as_deref().unwrap(),
["Tasks", "LoadAverage", "Uptime"]
);
assert!(Settings_validateMeters(&s));
}
#[test]
fn default_meters_replaces_prior_columns() {
let mut s = two_column_settings();
Settings_readMeters(&mut s, "Custom1 Custom2", 0);
Settings_readMeterModes(&mut s, "1 1", 0);
Settings_defaultMeters(&mut s, &host_with_cpus(2));
assert_eq!(
s.hColumns[0].names.as_deref().unwrap(),
["AllCPUs", "Memory", "Swap"]
);
assert_eq!(s.hColumns[0].len, 3);
}
#[test]
fn validate_meters_true_for_consistent_columns() {
let mut s = two_column_settings();
Settings_readMeters(&mut s, "AllCPUs Memory Swap", 0);
Settings_readMeterModes(&mut s, "1 1 1", 0);
Settings_readMeters(&mut s, "Tasks LoadAverage Uptime", 1);
Settings_readMeterModes(&mut s, "2 2 2", 1);
assert!(Settings_validateMeters(&s));
}
#[test]
fn validate_meters_false_when_names_missing_or_mismatched() {
let mut s = two_column_settings();
Settings_readMeterModes(&mut s, "1 1 1", 0);
assert!(!Settings_validateMeters(&s));
let mut s = two_column_settings();
Settings_readMeters(&mut s, "AllCPUs Memory", 0);
Settings_readMeterModes(&mut s, "1 1 1", 0);
assert!(!Settings_validateMeters(&s));
let mut s = two_column_settings();
Settings_readMeters(&mut s, "AllCPUs Memory Swap Extra", 0);
Settings_readMeterModes(&mut s, "1 1 1", 0);
assert!(!Settings_validateMeters(&s));
}
#[test]
fn validate_meters_false_when_no_column_has_meters() {
let s = two_column_settings();
assert!(!Settings_validateMeters(&s));
}
#[test]
fn write_list_space_joins_then_separator() {
let list = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let mut out = String::new();
writeList(&mut out, &list, list.len(), '\n');
assert_eq!(out, "a b c\n");
let mut out = String::new();
writeList(&mut out, &[], 0, ';');
assert_eq!(out, ";");
}
#[test]
fn write_meters_and_modes_roundtrip_read() {
let mut s = two_column_settings();
Settings_readMeters(&mut s, "AllCPUs Memory Swap", 0);
Settings_readMeterModes(&mut s, "1 1 1", 0);
let mut names_out = String::new();
writeMeters(&s, &mut names_out, '\n', 0);
assert_eq!(names_out, "AllCPUs Memory Swap\n");
let mut modes_out = String::new();
writeMeterModes(&s, &mut modes_out, '\n', 0);
assert_eq!(modes_out, "1 1 1\n");
}
#[test]
fn write_meters_empty_column_writes_bang() {
let s = two_column_settings();
let mut names_out = String::new();
writeMeters(&s, &mut names_out, '\n', 0);
assert_eq!(names_out, "!\n");
let mut modes_out = String::new();
writeMeterModes(&s, &mut modes_out, '\n', 0);
assert_eq!(modes_out, "!\n");
}
#[test]
fn set_header_layout_grows_shrinks_and_marks_changed() {
let mut s = two_column_settings();
Settings_setHeaderLayout(&mut s, HeaderLayout::HF_FOUR_25_25_25_25);
assert_eq!(s.hColumns.len(), 4);
assert_eq!(s.hLayout, HeaderLayout::HF_FOUR_25_25_25_25);
assert!(s.changed);
s.changed = false;
Settings_setHeaderLayout(&mut s, HeaderLayout::HF_ONE_100);
assert_eq!(s.hColumns.len(), 1);
assert_eq!(s.hLayout, HeaderLayout::HF_ONE_100);
assert!(s.changed);
s.changed = false;
Settings_setHeaderLayout(&mut s, HeaderLayout::HF_ONE_100);
assert_eq!(s.hColumns.len(), 1);
assert!(s.changed);
}
#[test]
fn header_layout_get_columns_counts() {
use HeaderLayout::*;
assert_eq!(HeaderLayout_getColumns(HF_ONE_100), 1);
assert_eq!(HeaderLayout_getColumns(HF_TWO_50_50), 2);
assert_eq!(HeaderLayout_getColumns(HF_TWO_33_67), 2);
assert_eq!(HeaderLayout_getColumns(HF_THREE_33_34_33), 3);
assert_eq!(HeaderLayout_getColumns(HF_THREE_40_20_40), 3);
assert_eq!(HeaderLayout_getColumns(HF_FOUR_25_25_25_25), 4);
}
#[test]
fn invert_sort_order_toggles_direction_when_not_treeview() {
let mut ss = ScreenSettings {
treeView: false,
direction: 1,
treeDirection: 1,
..Default::default()
};
ScreenSettings_invertSortOrder(&mut ss);
assert_eq!(ss.direction, -1);
assert_eq!(ss.treeDirection, 1);
ScreenSettings_invertSortOrder(&mut ss);
assert_eq!(ss.direction, 1);
}
#[test]
fn invert_sort_order_uses_tree_direction_when_treeview() {
let mut ss = ScreenSettings {
treeView: true,
direction: 1,
treeDirection: 1,
..Default::default()
};
ScreenSettings_invertSortOrder(&mut ss);
assert_eq!(ss.treeDirection, -1);
assert_eq!(ss.direction, 1); }
#[test]
fn invert_sort_order_non_one_becomes_one() {
let mut ss = ScreenSettings {
treeView: false,
direction: 5,
treeDirection: 0,
..Default::default()
};
ScreenSettings_invertSortOrder(&mut ss);
assert_eq!(ss.direction, 1);
}
#[test]
fn active_sort_key_picks_field_by_view_mode() {
let ss = ScreenSettings {
treeView: false,
sortKey: 47, treeSortKey: 49, treeViewAlwaysByPID: true,
..Default::default()
};
assert_eq!(ScreenSettings_getActiveSortKey(&ss), 47);
let ss = ScreenSettings {
treeView: true,
sortKey: 47,
treeSortKey: 49,
treeViewAlwaysByPID: false,
..Default::default()
};
assert_eq!(ScreenSettings_getActiveSortKey(&ss), 49);
let ss = ScreenSettings {
treeView: true,
sortKey: 47,
treeSortKey: 49,
treeViewAlwaysByPID: true,
..Default::default()
};
assert_eq!(ScreenSettings_getActiveSortKey(&ss), 1);
}
#[test]
fn active_direction_picks_by_view_mode() {
let ss = ScreenSettings {
treeView: false,
direction: -1,
treeDirection: 1,
..Default::default()
};
assert_eq!(ScreenSettings_getActiveDirection(&ss), -1);
let ss = ScreenSettings {
treeView: true,
direction: -1,
treeDirection: 1,
..Default::default()
};
assert_eq!(ScreenSettings_getActiveDirection(&ss), 1);
}
#[test]
fn readonly_latch_starts_false_then_latches_true() {
assert!(!Settings_isReadonly());
Settings_enableReadonly();
assert!(Settings_isReadonly());
}
#[test]
fn set_sort_key_flat_and_tree_modes() {
use crate::ported::process::ProcessField;
let mut ss = ScreenSettings {
treeView: false,
..Default::default()
};
ScreenSettings_setSortKey(&mut ss, ProcessField::PERCENT_CPU as RowField);
assert_eq!(ss.sortKey, ProcessField::PERCENT_CPU as RowField);
assert_eq!(ss.direction, -1);
assert!(!ss.treeView);
ScreenSettings_setSortKey(&mut ss, ProcessField::PID as RowField);
assert_eq!(ss.direction, 1);
let mut ss = ScreenSettings {
treeView: true,
treeViewAlwaysByPID: false,
..Default::default()
};
ScreenSettings_setSortKey(&mut ss, ProcessField::PERCENT_CPU as RowField);
assert_eq!(ss.treeSortKey, ProcessField::PERCENT_CPU as RowField);
assert_eq!(ss.treeDirection, -1);
assert!(ss.treeView);
}
#[test]
fn to_field_name_and_index_reserved() {
use crate::ported::hashtable::Hashtable_new;
use crate::ported::process::ProcessField;
let ht = Hashtable_new(0, false);
assert_eq!(
toFieldName(&ht, ProcessField::PID as i32, None),
Some("PID")
);
assert_eq!(toFieldName(&ht, -1, None), None);
assert_eq!(toFieldIndex(&ht, "0"), ProcessField::PID as i32);
assert_eq!(toFieldIndex(&ht, "Command"), ProcessField::COMM as i32);
assert_eq!(toFieldIndex(&ht, "RCHAR"), ProcessField::RCHAR as i32);
assert_eq!(toFieldIndex(&ht, "Nonexistent"), -1);
}
#[test]
fn read_fields_parses_ids_and_flags() {
use crate::ported::hashtable::Hashtable_new;
use crate::ported::process::{ProcessField, PROCESS_FLAG_IO};
let ht = Hashtable_new(0, false);
let mut ss = ScreenSettings::default();
ScreenSettings_readFields(&mut ss, &ht, "0 1");
assert_eq!(
ss.fields,
vec![
ProcessField::PID as RowField,
ProcessField::COMM as RowField
]
);
assert_eq!(ss.flags, 0);
let mut ss = ScreenSettings::default();
ScreenSettings_readFields(&mut ss, &ht, "RCHAR");
assert_eq!(ss.fields, vec![ProcessField::RCHAR as RowField]);
assert_eq!(ss.flags, PROCESS_FLAG_IO);
}
#[test]
fn write_fields_by_name_and_numeric_form() {
use crate::ported::hashtable::Hashtable_new;
use crate::ported::process::ProcessField;
let ht = Hashtable_new(0, false);
let fields = vec![
ProcessField::PID as RowField,
ProcessField::COMM as RowField,
];
let mut out = String::new();
writeFields(&mut out, &fields, &ht, true, '\n');
assert_eq!(out, "PID Command\n");
let mut ss = ScreenSettings::default();
ScreenSettings_readFields(&mut ss, &ht, out.trim_end());
assert_eq!(ss.fields, fields);
let mut out = String::new();
writeFields(&mut out, &fields, &ht, false, '\n');
assert_eq!(
out,
format!(
"{} {}\n",
ProcessField::PID as i32 - 1,
ProcessField::COMM as i32 - 1
)
);
let mut out = String::new();
writeFields(&mut out, &[], &ht, true, ';');
assert_eq!(out, ";");
}
#[test]
fn read_missing_file_returns_false_and_sets_write_config() {
let mut s = Settings::default();
let path = "/nonexistent/htoprs-test/does-not-exist/htoprc";
let host = host_with_cpus(4);
let ok = Settings_read(&mut s, path, &host, true);
assert!(!ok, "missing file must return false");
assert!(s.writeConfig, "ENOENT must set writeConfig = true");
}
#[test]
fn default_screens_creates_main_screen() {
let mut s = Settings::default();
assert!(s.screens.is_empty());
let idx = Settings_defaultScreens(&mut s);
assert_eq!(idx, 0);
assert_eq!(s.screens.len(), 1);
assert_eq!(s.screens[0].heading.as_deref(), Some("Main"));
let idx2 = Settings_defaultScreens(&mut s);
assert_eq!(idx2, 0);
assert_eq!(s.screens.len(), 1);
}
}