#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)] #![allow(dead_code)]
use crate::ported::action::{
Action_setBindings, Action_setScreenTab, Action_setSortKey, Htop_Action, Htop_Reaction, State,
HTOP_KEEP_FOLLOWING, HTOP_OK, HTOP_QUIT, HTOP_RECALCULATE, HTOP_REDRAW_BAR, HTOP_REFRESH,
HTOP_RESIZE, HTOP_SAVE_SETTINGS, HTOP_UPDATE_PANELHDR,
};
use crate::ported::crt::{
ColorElements, ColorScheme, ERR, KEY_F, KEY_LEFT, KEY_MAX, KEY_MOUSE, KEY_RESIZE, KEY_RIGHT,
};
use crate::ported::functionbar::{
FunctionBar, FunctionBar_append, FunctionBar_delete, FunctionBar_new, FunctionBar_setLabel,
};
use crate::ported::incset::{
IncSet, IncSet_delete, IncSet_drawBar, IncSet_filter, IncSet_handleKey, IncSet_new,
};
use crate::ported::object::{Arg, Object, Object_class};
use crate::ported::panel::{
HandlerResult, Panel, PanelClass, Panel_get, Panel_getSelected, Panel_new, Panel_setSelected,
Panel_setSelectionColor, Panel_size,
};
use crate::ported::row::RowField_keyAt;
use crate::ported::settings::{
ScreenSettings_getActiveSortKey, ScreenSettings_invertSortOrder, Settings_isReadonly,
};
use crate::ported::table::Table_printHeader;
use crate::ported::vector::Vector_new;
#[cfg(target_os = "macos")]
use crate::ported::darwin::platform::Platform_setBindings;
#[cfg(target_os = "freebsd")]
use crate::ported::freebsd::platform::Platform_setBindings;
#[cfg(target_os = "linux")]
use crate::ported::linux::platform::Platform_setBindings;
#[cfg(target_os = "netbsd")]
use crate::ported::netbsd::platform::Platform_setBindings;
#[cfg(target_os = "openbsd")]
use crate::ported::openbsd::platform::Platform_setBindings;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
use crate::ported::solaris::platform::Platform_setBindings;
#[cfg(not(any(
target_os = "macos",
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris",
target_os = "illumos"
)))]
use crate::ported::unsupported::platform::Platform_setBindings;
pub struct MainPanel {
pub super_: Panel,
pub state: *mut State,
pub inc: IncSet,
pub processBar: FunctionBar,
pub readonlyBar: FunctionBar,
pub keys: Vec<Option<Htop_Action>>,
pub idSearch: u32,
}
impl PanelClass for MainPanel {
fn as_panel(&self) -> &Panel {
&self.super_
}
fn as_panel_mut(&mut self) -> &mut Panel {
&mut self.super_
}
fn is_text_input_active(&self) -> bool {
self.inc.active.is_some()
}
fn event_handler(&mut self, ev: i32) -> HandlerResult {
MainPanel_eventHandler(self, ev)
}
fn draw_function_bar(&mut self, hide_function_bar: bool) {
MainPanel_drawFunctionBar(self, hide_function_bar)
}
fn print_header(&mut self) {
MainPanel_printHeader(self)
}
}
pub type MainPanel_foreachRowFn = fn(&mut dyn Object, Arg) -> bool;
const MainFunctions: [&str; 10] = [
"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", "Nice -", "Nice +", "Kill ",
"Quit ",
];
const MainFunctions_ro: [&str; 10] = [
"Help ", "Setup ", "Search", "Filter", "Tree ", "SortBy", " ", " ", " ",
"Quit ",
];
fn EVENT_IS_HEADER_CLICK(ev: i32) -> bool {
(-10000..=-9000).contains(&ev)
}
fn EVENT_HEADER_CLICK_GET_X(ev: i32) -> i32 {
ev + 10000
}
fn EVENT_IS_SCREEN_TAB_CLICK(ev: i32) -> bool {
(-20000..-10000).contains(&ev)
}
fn EVENT_SCREEN_TAB_GET_X(ev: i32) -> i32 {
ev + 20000
}
pub fn MainPanel_updateLabels(this: &mut MainPanel, list: bool, filter: bool) {
if let Some(bar) = this.super_.defaultBar.as_mut() {
FunctionBar_setLabel(bar, KEY_F(5), if list { "List " } else { "Tree " });
FunctionBar_setLabel(bar, KEY_F(4), if filter { "FILTER" } else { "Filter" });
}
}
fn MainPanel_idSearch(this: &mut MainPanel, ch: i32) {
let id: i32 = ch - 48 + this.idSearch as i32;
let size = Panel_size(&this.super_);
for i in 0..size {
let matches = {
let obj: &dyn Object = Panel_get(&this.super_, i);
obj.as_row().is_some_and(|row| row.id == id)
};
if matches {
Panel_setSelected(&mut this.super_, i);
break;
}
}
this.idSearch = (id * 10) as u32;
if this.idSearch > 10000000 {
this.idSearch = 0;
}
}
pub fn MainPanel_getValue(this: &Panel, i: i32) -> &str {
let row = Panel_get(this, i);
match row
.row_class()
.and_then(|klass| klass.sortKeyString)
.and_then(|slot| slot(row))
{
Some(bytes) => core::str::from_utf8(bytes).unwrap_or(""),
None => "",
}
}
pub fn MainPanel_eventHandler(this: &mut MainPanel, ch: i32) -> HandlerResult {
let state = this.state;
let host = unsafe { (*state).host };
let mut reaction: Htop_Reaction = HTOP_OK;
let mut result: HandlerResult = HandlerResult::IGNORED;
if ch == KEY_RESIZE {
return HandlerResult::IGNORED;
}
let mut needReset = ch != ERR;
let enableMouse = unsafe {
(*host)
.settings
.as_ref()
.expect("MainPanel_eventHandler: host->settings is NULL")
.enableMouse
};
if !(ch != KEY_MOUSE || enableMouse) {
needReset = false;
}
if needReset {
unsafe {
(*state).hideSelection = false;
}
}
if EVENT_IS_HEADER_CLICK(ch) {
let x = EVENT_HEADER_CLICK_GET_X(ch);
let hx = this.super_.scrollH + x + 1;
let ssidx = unsafe { (*host).settings.as_ref().unwrap().ssIndex as usize };
let field = unsafe { RowField_keyAt((*host).settings.as_ref().unwrap(), hx) };
let (treeView, alwaysByPID) = unsafe {
let s = &(*host).settings.as_ref().unwrap().screens[ssidx];
(s.treeView, s.treeViewAlwaysByPID)
};
if treeView && alwaysByPID {
unsafe {
let s = &mut (*host).settings.as_mut().unwrap().screens[ssidx];
s.treeView = false;
s.direction = 1;
}
reaction |= unsafe { Action_setSortKey((*host).settings.as_mut().unwrap(), field) };
} else if field
== unsafe {
ScreenSettings_getActiveSortKey(&(*host).settings.as_ref().unwrap().screens[ssidx])
}
{
unsafe {
ScreenSettings_invertSortOrder(
&mut (*host).settings.as_mut().unwrap().screens[ssidx],
);
}
} else {
reaction |= unsafe { Action_setSortKey((*host).settings.as_mut().unwrap(), field) };
}
reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR | HTOP_SAVE_SETTINGS;
result = HandlerResult::HANDLED;
} else if EVENT_IS_SCREEN_TAB_CLICK(ch) {
let x = EVENT_SCREEN_TAB_GET_X(ch);
reaction |= Action_setScreenTab(unsafe { &*state }, x);
result = HandlerResult::HANDLED;
} else if ch != ERR && this.inc.active.is_some() {
let mut lines = Vector_new(&Object_class, false, 10);
let filterChanged = IncSet_handleKey(
&mut this.inc,
ch,
&mut this.super_,
MainPanel_getValue,
&mut lines,
);
if filterChanged {
let filter = IncSet_filter(&this.inc).map(|s| s.to_string());
let at = unsafe {
(*host)
.activeTable
.expect("MainPanel_eventHandler: host->activeTable is NULL")
};
unsafe {
(*at).incFilter = filter;
}
reaction = HTOP_REFRESH | HTOP_REDRAW_BAR;
}
let followFound = this.inc.found
&& this
.inc
.active
.is_some_and(|a| !this.inc.modes[a as usize].isFilter);
if followFound {
let sel = MainPanel_selectedRow(this);
let at = unsafe {
(*host)
.activeTable
.expect("MainPanel_eventHandler: host->activeTable is NULL")
};
unsafe {
(*at).following = sel;
}
Panel_setSelectionColor(&mut this.super_, ColorElements::PANEL_SELECTION_FOLLOW);
reaction |= HTOP_KEEP_FOLLOWING;
}
result = HandlerResult::HANDLED;
} else if ch == 27 {
unsafe {
(*state).hideSelection = true;
}
return HandlerResult::HANDLED;
} else if ch != ERR && ch > 0 && ch < KEY_MAX && this.keys[ch as usize].is_some() {
let handler = this.keys[ch as usize].unwrap();
reaction |= handler(unsafe { &mut *state });
result = HandlerResult::HANDLED;
} else if 0 < ch && ch < 255 && (ch as u8).is_ascii_digit() {
MainPanel_idSearch(this, ch);
} else if ch == KEY_LEFT || ch == KEY_RIGHT {
reaction |= HTOP_KEEP_FOLLOWING;
} else {
if ch != ERR {
this.idSearch = 0;
} else {
reaction |= HTOP_KEEP_FOLLOWING;
}
}
if reaction & HTOP_REDRAW_BAR == HTOP_REDRAW_BAR {
let treeView = unsafe {
let s = (*host).settings.as_ref().unwrap();
s.screens[s.ssIndex as usize].treeView
};
let filter = unsafe {
let at = (*host)
.activeTable
.expect("MainPanel_eventHandler: host->activeTable is NULL");
(*at).incFilter.is_some()
};
MainPanel_updateLabels(this, treeView, filter);
}
if reaction & HTOP_RESIZE == HTOP_RESIZE {
result |= HandlerResult::RESIZE;
}
if reaction & HTOP_UPDATE_PANELHDR == HTOP_UPDATE_PANELHDR {
result |= HandlerResult::REDRAW;
}
if reaction & HTOP_REFRESH == HTOP_REFRESH {
result |= HandlerResult::REFRESH;
}
if reaction & HTOP_RECALCULATE == HTOP_RECALCULATE {
result |= HandlerResult::RESCAN;
}
if reaction & HTOP_SAVE_SETTINGS == HTOP_SAVE_SETTINGS {
unsafe {
(*host).settings.as_mut().unwrap().changed = true;
}
}
if reaction & HTOP_QUIT == HTOP_QUIT {
return HandlerResult::BREAK_LOOP;
}
if reaction & HTOP_KEEP_FOLLOWING != HTOP_KEEP_FOLLOWING {
let at = unsafe {
(*host)
.activeTable
.expect("MainPanel_eventHandler: host->activeTable is NULL")
};
unsafe {
(*at).following = -1;
}
Panel_setSelectionColor(&mut this.super_, ColorElements::PANEL_SELECTION_FOCUS);
}
result
}
pub fn MainPanel_selectedRow(this: &MainPanel) -> i32 {
match Panel_getSelected(&this.super_) {
Some(obj) => obj.as_row().map_or(-1, |row| row.id),
None => -1,
}
}
pub fn MainPanel_foreachRow(
this: &mut MainPanel,
fn_: MainPanel_foreachRowFn,
arg: Arg,
wasAnyTagged: Option<&mut bool>,
) -> bool {
let mut ok = true;
let mut anyTagged = false;
let size = Panel_size(&this.super_);
for i in 0..size {
let obj: &mut dyn Object = this.super_.items[i as usize].object_mut();
let tagged = obj.as_row().is_some_and(|r| r.tag);
if tagged {
ok &= fn_(obj, arg);
anyTagged = true;
}
}
if !anyTagged {
let sel = this.super_.selected;
if sel >= 0 && (sel as usize) < this.super_.items.len() {
let obj: &mut dyn Object = this.super_.items[sel as usize].object_mut();
ok &= fn_(obj, arg);
}
}
if let Some(w) = wasAnyTagged {
*w = anyTagged;
}
ok
}
pub fn MainPanel_drawFunctionBar(this: &mut MainPanel, hideFunctionBar: bool) {
if hideFunctionBar && this.inc.active.is_none() {
return;
}
IncSet_drawBar(
&mut this.inc,
&mut this.super_,
ColorElements::FUNCTION_BAR.packed(ColorScheme::active()),
);
let state = unsafe { &*this.state };
if state.pauseUpdate {
FunctionBar_append(
"PAUSED",
ColorElements::PAUSED.packed(ColorScheme::active()),
);
} else if let Some(msg) = &state.failedUpdate {
FunctionBar_append(
msg,
ColorElements::FAILED_READ.packed(ColorScheme::active()),
);
}
}
pub fn MainPanel_printHeader(this: &mut MainPanel) {
let host = unsafe { &*(*this.state).host };
let settings = host
.settings
.as_ref()
.expect("MainPanel_printHeader: host->settings is NULL");
Table_printHeader(settings, &mut this.super_.header);
}
pub fn MainPanel_new() -> MainPanel {
let processBar = FunctionBar_new(Some(&MainFunctions), None, None);
let readonlyBar = FunctionBar_new(Some(&MainFunctions_ro), None, None);
let activeBar = if Settings_isReadonly() {
readonlyBar.clone()
} else {
processBar.clone()
};
let super_ = Panel_new(1, 1, 1, 1, Some(activeBar.clone()));
let mut keys: Vec<Option<Htop_Action>> = vec![None; KEY_MAX as usize];
let inc = IncSet_new(Some(activeBar));
Action_setBindings(&mut keys);
Platform_setBindings(&mut keys);
MainPanel {
super_,
state: core::ptr::null_mut(),
inc,
processBar,
readonlyBar,
keys,
idSearch: 0,
}
}
pub fn MainPanel_setState(this: &mut MainPanel, state: *mut State) {
this.state = state;
}
pub fn MainPanel_setFunctionBar(this: &mut MainPanel, readonly: bool) {
let bar = if readonly {
this.readonlyBar.clone()
} else {
this.processBar.clone()
};
this.super_.defaultBar = Some(bar);
this.inc.defaultBar = this.super_.defaultBar.clone();
}
pub fn MainPanel_delete(this: MainPanel) {
let MainPanel {
super_,
inc,
processBar,
readonlyBar,
..
} = this;
FunctionBar_delete(processBar);
FunctionBar_delete(readonlyBar);
IncSet_delete(inc);
let _ = super_;
}
#[cfg(test)]
use crate::ported::panel::PanelItem;
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::functionbar::FunctionBar_new;
use crate::ported::incset::IncSet_new;
use crate::ported::panel::Panel_new;
use crate::ported::row::Row;
use core::any::Any;
fn blank() -> MainPanel {
MainPanel {
super_: Panel_new(1, 1, 1, 1, None),
state: core::ptr::null_mut(),
inc: IncSet_new(None),
processBar: FunctionBar_new(None, None, None),
readonlyBar: FunctionBar_new(None, None, None),
keys: vec![None; KEY_MAX as usize],
idSearch: 0,
}
}
fn row(id: i32) -> Box<dyn Object> {
Box::new(Row {
id,
..Row::default()
})
}
fn f5f4_bar() -> FunctionBar {
FunctionBar_new(
Some(&["Tree ", "Filter"]),
Some(&["F5", "F4"]),
Some(&[KEY_F(5), KEY_F(4)]),
)
}
#[test]
fn update_labels_list_and_filter_active() {
let mut mp = blank();
mp.super_.defaultBar = Some(f5f4_bar());
MainPanel_updateLabels(&mut mp, true, true);
let bar = mp.super_.defaultBar.as_ref().unwrap();
assert_eq!(bar.functions[0], "List "); assert_eq!(bar.functions[1], "FILTER"); }
#[test]
fn update_labels_tree_and_filter_inactive() {
let mut mp = blank();
mp.super_.defaultBar = Some(f5f4_bar());
MainPanel_updateLabels(&mut mp, false, false);
let bar = mp.super_.defaultBar.as_ref().unwrap();
assert_eq!(bar.functions[0], "Tree "); assert_eq!(bar.functions[1], "Filter"); }
#[test]
fn update_labels_no_bar_is_noop() {
let mut mp = blank();
mp.super_.defaultBar = None;
MainPanel_updateLabels(&mut mp, true, true);
assert!(mp.super_.defaultBar.is_none());
}
#[test]
fn selected_row_empty_is_minus_one() {
let mp = blank();
assert_eq!(MainPanel_selectedRow(&mp), -1);
}
#[test]
fn selected_row_returns_selected_id() {
let mut mp = blank();
mp.super_.items.push(PanelItem::Owned(row(100)));
mp.super_.items.push(PanelItem::Owned(row(200)));
mp.super_.items.push(PanelItem::Owned(row(300)));
mp.super_.selected = 1;
assert_eq!(MainPanel_selectedRow(&mp), 200);
mp.super_.selected = 2;
assert_eq!(MainPanel_selectedRow(&mp), 300);
}
#[test]
fn id_search_selects_matching_row_and_accumulates() {
let mut mp = blank();
mp.super_.items.push(PanelItem::Owned(row(1)));
mp.super_.items.push(PanelItem::Owned(row(12)));
mp.super_.items.push(PanelItem::Owned(row(123)));
MainPanel_idSearch(&mut mp, b'1' as i32);
assert_eq!(mp.super_.selected, 0);
assert_eq!(mp.idSearch, 10);
MainPanel_idSearch(&mut mp, b'2' as i32);
assert_eq!(mp.super_.selected, 1);
assert_eq!(mp.idSearch, 120);
MainPanel_idSearch(&mut mp, b'3' as i32);
assert_eq!(mp.super_.selected, 2);
assert_eq!(mp.idSearch, 1230);
}
#[test]
fn id_search_no_match_keeps_selection_but_advances() {
let mut mp = blank();
mp.super_.items.push(PanelItem::Owned(row(1)));
mp.super_.items.push(PanelItem::Owned(row(2)));
mp.super_.selected = 1;
MainPanel_idSearch(&mut mp, b'9' as i32);
assert_eq!(mp.super_.selected, 1);
assert_eq!(mp.idSearch, 90);
}
#[test]
fn id_search_rolls_over_past_ten_million() {
let mut mp = blank();
mp.super_.items.push(PanelItem::Owned(row(1)));
mp.idSearch = 1_000_001; MainPanel_idSearch(&mut mp, b'0' as i32);
assert_eq!(mp.idSearch, 0);
}
fn visit_cb(obj: &mut dyn Object, arg: Arg) -> bool {
if let Arg::V(p) = arg {
unsafe {
*(p as *mut i32) += 1;
}
}
if let Some(row) = obj.as_row_mut() {
row.indent = 99;
}
true
}
fn fail_cb(_obj: &mut dyn Object, _arg: Arg) -> bool {
false
}
#[test]
fn foreach_row_applies_to_tagged_only() {
let mut mp = blank();
for id in [10, 20, 30] {
mp.super_.items.push(PanelItem::Owned(row(id)));
}
{
let a: &mut dyn Any = mp.super_.items[0].object_mut();
a.downcast_mut::<Row>().unwrap().tag = true;
}
{
let a: &mut dyn Any = mp.super_.items[2].object_mut();
a.downcast_mut::<Row>().unwrap().tag = true;
}
let mut count: i32 = 0;
let mut any_tagged = false;
let arg = Arg::V(&mut count as *mut i32 as *mut core::ffi::c_void);
let ok = MainPanel_foreachRow(&mut mp, visit_cb, arg, Some(&mut any_tagged));
assert!(ok);
assert!(any_tagged);
assert_eq!(count, 2); let indent_of = |i: usize, mp: &mut MainPanel| -> i32 {
let a: &mut dyn Any = mp.super_.items[i].object_mut();
a.downcast_mut::<Row>().unwrap().indent
};
assert_eq!(indent_of(0, &mut mp), 99);
assert_eq!(indent_of(1, &mut mp), 0);
assert_eq!(indent_of(2, &mut mp), 99);
}
#[test]
fn foreach_row_falls_back_to_selected_when_none_tagged() {
let mut mp = blank();
for id in [10, 20, 30] {
mp.super_.items.push(PanelItem::Owned(row(id)));
}
mp.super_.selected = 1;
let mut count: i32 = 0;
let mut any_tagged = true; let arg = Arg::V(&mut count as *mut i32 as *mut core::ffi::c_void);
let ok = MainPanel_foreachRow(&mut mp, visit_cb, arg, Some(&mut any_tagged));
assert!(ok);
assert!(!any_tagged);
assert_eq!(count, 1); let a: &mut dyn Any = mp.super_.items[1].object_mut();
assert_eq!(a.downcast_mut::<Row>().unwrap().indent, 99);
}
#[test]
fn foreach_row_ands_the_callback_results() {
let mut mp = blank();
mp.super_.items.push(PanelItem::Owned(row(10)));
{
let a: &mut dyn Any = mp.super_.items[0].object_mut();
a.downcast_mut::<Row>().unwrap().tag = true;
}
let ok = MainPanel_foreachRow(&mut mp, fail_cb, Arg::I(0), None);
assert!(!ok); }
#[test]
fn foreach_row_wastagged_out_param_is_optional() {
let mut mp = blank();
mp.super_.items.push(PanelItem::Owned(row(10)));
mp.super_.selected = 0;
let ok = MainPanel_foreachRow(&mut mp, visit_cb, Arg::I(0), None);
assert!(ok);
}
#[test]
fn set_state_stores_pointer() {
let mut mp = blank();
let mut st = State {
host: core::ptr::null_mut(),
mainPanel: core::ptr::null_mut(),
header: core::ptr::null_mut(),
failedUpdate: None,
pauseUpdate: false,
hideSelection: false,
hideMeters: false,
};
MainPanel_setState(&mut mp, &mut st as *mut State);
assert_eq!(mp.state, &mut st as *mut State);
}
#[test]
fn set_function_bar_selects_readonly_or_process() {
let mut mp = blank();
mp.processBar = FunctionBar_new(Some(&["PROC"]), Some(&["F1"]), Some(&[1]));
mp.readonlyBar = FunctionBar_new(Some(&["RO"]), Some(&["F1"]), Some(&[1]));
MainPanel_setFunctionBar(&mut mp, true);
assert_eq!(
mp.super_.defaultBar.as_ref().unwrap().functions,
vec!["RO".to_string()]
);
assert_eq!(
mp.inc.defaultBar.as_ref().unwrap().functions,
vec!["RO".to_string()]
);
MainPanel_setFunctionBar(&mut mp, false);
assert_eq!(
mp.super_.defaultBar.as_ref().unwrap().functions,
vec!["PROC".to_string()]
);
assert_eq!(
mp.inc.defaultBar.as_ref().unwrap().functions,
vec!["PROC".to_string()]
);
}
}