diff --git a/local/recipes/system/redbear-power/source/src/app.rs b/local/recipes/system/redbear-power/source/src/app.rs index ca33bfe997..e3a2117879 100644 --- a/local/recipes/system/redbear-power/source/src/app.rs +++ b/local/recipes/system/redbear-power/source/src/app.rs @@ -150,6 +150,7 @@ pub struct App { /// Toggled by the `T` hotkey. Sort modes are honored within /// each parent's children but parents always come first. pub process_tree: bool, + pub show_full_cmdline: bool, /// PIDs whose subtrees are collapsed in tree view. When a PID /// is in this set, its descendants are not rendered. Toggled /// by the `Space` hotkey (Process tab, tree mode only). @@ -443,6 +444,7 @@ impl App { process_filter: String::new(), sched_stats: crate::sched::SchedStats::default(), process_tree: false, + show_full_cmdline: false, folded: std::collections::BTreeSet::new(), pkg_power_w: None, rapl_prev: None, diff --git a/local/recipes/system/redbear-power/source/src/graph.rs b/local/recipes/system/redbear-power/source/src/graph.rs index 882facd9af..91c9d773d6 100644 --- a/local/recipes/system/redbear-power/source/src/graph.rs +++ b/local/recipes/system/redbear-power/source/src/graph.rs @@ -124,17 +124,10 @@ impl Widget for BrailleGraph<'_> { // Render optional x-axis labels. if let (Some((min_label, max_label)), true) = (self.x_labels, inner.width >= 10) { let label_y = inner.y + inner.height.saturating_sub(1); - // Left label - if let Some(cell) = buf.cell_mut((inner.x, label_y)) { - cell.set_symbol(&min_label[..min_label.len().min(1)]) - .set_style(Style::new().dark_gray()); - } - // Right label + let dim = Style::new().dark_gray(); + buf.set_string(inner.x, label_y, min_label, dim); let right_x = inner.x + inner.width.saturating_sub(max_label.len() as u16); - if let Some(cell) = buf.cell_mut((right_x, label_y)) { - cell.set_symbol(&max_label[..max_label.len().min(1)]) - .set_style(Style::new().dark_gray()); - } + buf.set_string(right_x, label_y, max_label, dim); } } } diff --git a/local/recipes/system/redbear-power/source/src/kill.rs b/local/recipes/system/redbear-power/source/src/kill.rs index f8cc788956..deadb90bcd 100644 --- a/local/recipes/system/redbear-power/source/src/kill.rs +++ b/local/recipes/system/redbear-power/source/src/kill.rs @@ -106,8 +106,6 @@ impl Widget for &KillDialog { return; } - Clear.render(area, buf); - let dialog_area = Rect { x: area.x + (area.width.saturating_sub(50) / 2).min(area.x), y: area.y + (area.height.saturating_sub(12) / 2).min(area.y), @@ -115,6 +113,8 @@ impl Widget for &KillDialog { height: 12.min(area.height), }; + Clear.render(dialog_area, buf); + let block = Block::default() .borders(Borders::ALL) .border_style(Style::new().red().bold()) diff --git a/local/recipes/system/redbear-power/source/src/main.rs b/local/recipes/system/redbear-power/source/src/main.rs index d7b50bb54c..55ac71eb80 100644 --- a/local/recipes/system/redbear-power/source/src/main.rs +++ b/local/recipes/system/redbear-power/source/src/main.rs @@ -458,7 +458,7 @@ fn main() -> io::Result<()> { color: Color::Cyan, title: format!(" CPU {:.0}% ", cur), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some(("100".into(), " 0".into())), theme: &app.theme, }, @@ -475,7 +475,7 @@ fn main() -> io::Result<()> { color: Color::Red, title: format!(" Temp {:.0}\u{B0}C ", cur_t), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_t), "0".into())), theme: &app.theme, }, @@ -492,7 +492,7 @@ fn main() -> io::Result<()> { color: Color::Yellow, title: format!(" Pwr {:.1}W ", cur_p), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_p), "0".into())), theme: &app.theme, }, @@ -521,7 +521,7 @@ fn main() -> io::Result<()> { color: Color::Green, title: format!(" Net {:.0} KiB/s ", cur_n), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_n), "0".into())), theme: &app.theme, }, @@ -549,7 +549,7 @@ fn main() -> io::Result<()> { color: Color::Magenta, title: format!(" Disk {:.0} KiB/s ", cur_s), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_s), "0".into())), theme: &app.theme, }, @@ -589,7 +589,6 @@ fn main() -> io::Result<()> { } f.render_widget(&app.kill_dialog, full); if app.nice_edit.open { - f.render_widget(Clear, full); f.render_widget(&app.nice_edit, full); } if app.show_open_files { @@ -682,7 +681,7 @@ fn main() -> io::Result<()> { color: Color::Cyan, title: format!(" CPU {:.0}% ", cur), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some(("100".into(), " 0".into())), theme: &app.theme, }; @@ -697,7 +696,7 @@ fn main() -> io::Result<()> { color: Color::Red, title: format!(" Temp {:.0}\u{B0}C ", cur_t), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_t), "0".into())), theme: &app.theme, }; @@ -712,7 +711,7 @@ fn main() -> io::Result<()> { color: Color::Yellow, title: format!(" Pwr {:.1}W ", cur_p), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_p), "0".into())), theme: &app.theme, }; @@ -754,7 +753,7 @@ fn main() -> io::Result<()> { color: Color::Green, title: " Net Throughput KiB/s ".to_string(), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_n), "0".into())), theme: &app.theme, }; @@ -781,7 +780,7 @@ fn main() -> io::Result<()> { color: Color::Magenta, title: " Disk Throughput KiB/s ".to_string(), focused: false, - x_labels: None, + x_labels: Some(("-60s", "now")), y_labels: Some((format!("{:.0}", max_s), "0".into())), theme: &app.theme, }, @@ -1154,6 +1153,17 @@ fn main() -> io::Result<()> { if app.process_tree { "tree" } else { "flat" } )); } + Key::Char('C') if app.current_tab == TabId::Process => { + app.show_full_cmdline = !app.show_full_cmdline; + app.flash_status(format!( + "cmdline: {}", + if app.show_full_cmdline { + "full args" + } else { + "comm only" + } + )); + } Key::Char(' ') if app.process_tree => { if let Some(pid) = app.selected_pid() { if app.folded.contains(&pid) { diff --git a/local/recipes/system/redbear-power/source/src/process.rs b/local/recipes/system/redbear-power/source/src/process.rs index 6709c154ed..a79a4c5d7b 100644 --- a/local/recipes/system/redbear-power/source/src/process.rs +++ b/local/recipes/system/redbear-power/source/src/process.rs @@ -383,6 +383,7 @@ fn dfs_emit( pub struct ProcessInfo { pub pid: u32, pub comm: String, + pub cmdline: Option, pub state: char, /// Parent PID. Read by `sort_tree` (v1.27+) and `tree_prefix` /// to build the parent-child ordering in tree view. @@ -815,6 +816,7 @@ fn parse_stat_line(line: &str) -> Option { Some(ProcessInfo { pid, comm, + cmdline: None, state: state_char, ppid, utime, @@ -847,6 +849,15 @@ fn read_process(pid: u32) -> Option { if info.comm.is_empty() || info.comm == "?" { info.comm = read_comm(pid); } + info.cmdline = fs::read(format!("/proc/{}/cmdline", pid)) + .ok() + .map(|data| { + String::from_utf8_lossy(&data) + .replace('\0', " ") + .trim() + .to_string() + }) + .filter(|s| !s.is_empty()); info.sched_policy = derive_sched_policy(pid, info.priority); Some(info) } diff --git a/local/recipes/system/redbear-power/source/src/render.rs b/local/recipes/system/redbear-power/source/src/render.rs index ba667b0034..c45fa3f216 100644 --- a/local/recipes/system/redbear-power/source/src/render.rs +++ b/local/recipes/system/redbear-power/source/src/render.rs @@ -1201,7 +1201,14 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { let is_cursor = app.current_tab == TabId::Process && visible_index == app.process_cursor && focused; visible_index += 1; - let comm_truncated: String = p.comm.chars().take(20).collect(); + let display_name: String = if app.show_full_cmdline { + p.cmdline + .as_ref() + .map(|c| c.chars().take(40).collect()) + .unwrap_or_else(|| p.comm.chars().take(20).collect()) + } else { + p.comm.chars().take(20).collect() + }; let io_str = match p.io_total_kb() { Some(kb) => crate::process::ProcessInfo::format_memory_kb(kb), None => "—".to_string(), @@ -1365,30 +1372,27 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { spans.push(Span::styled(cpu_str, cpu_style)); } spans.push(Span::styled(post, row_style)); - if !filter_lower.is_empty() && comm_truncated.to_lowercase().contains(&filter_lower) { - let lower = comm_truncated.to_lowercase(); + if !filter_lower.is_empty() && display_name.to_lowercase().contains(&filter_lower) { + let lower = display_name.to_lowercase(); let mut last = 0; while let Some(pos) = lower[last..].find(&filter_lower) { let abs = last + pos; if abs > last { - spans.push(Span::styled( - comm_truncated[last..abs].to_string(), - row_style, - )); + spans.push(Span::styled(display_name[last..abs].to_string(), row_style)); } spans.push(Span::styled( - comm_truncated[abs..abs + filter_lower.len()].to_string(), + display_name[abs..abs + filter_lower.len()].to_string(), row_style.add_modifier( ratatui::style::Modifier::BOLD | ratatui::style::Modifier::REVERSED, ), )); last = abs + filter_lower.len(); } - if last < comm_truncated.len() { - spans.push(Span::styled(comm_truncated[last..].to_string(), row_style)); + if last < display_name.len() { + spans.push(Span::styled(display_name[last..].to_string(), row_style)); } } else { - spans.push(Span::styled(comm_truncated, row_style)); + spans.push(Span::styled(display_name, row_style)); } lines.push(Line::from(spans)); } @@ -2034,7 +2038,7 @@ pub fn render_cpu_table<'a>( pub fn render_keybar<'a>(app: &'a App) -> Paragraph<'a> { let gov = format!(" g:{}", app.cpufreq.active.as_str()); let tab_hints: &str = if app.current_tab == crate::app::TabId::Process { - " ↑↓:sel k:kill N:nice l:files o:sort i:invert F:filter y:tree Enter:detail" + " ↑↓:sel k:kill N:nice l:files C:cmd o:sort i:invert F:filter y:tree Enter:detail" } else { " ↑↓:cpu p/P:±pstate m/M:min/max t:throttle r:refresh" }; @@ -2145,6 +2149,7 @@ TABS: [k] kill selected process (Process tab only) [N] edit nice value of selected process (Process tab) [l] list open file descriptors of selected process (Process tab) + [C] toggle full command line vs comm name (Process tab) [y] toggle process tree view (Process tab) [F] filter processes by name (Process tab) [o] cycle process sort mode