mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 15:44:17 +02:00
feature: Fine grained kill signals on unix (#263)
* feature: added signal selection for killing in unix * feature: set default signal to 15 (TERM) * feature: selecting kill signal number with number keys * feature: mouse selection of kill signals * fix: restore working previous kill dialog for win * bug: more fixes for killing on windows * feature: made two digit number selection only work in time window * feature: replaced grid with scrollable list for kill signal selection * fix: handling scrolling myself * chore: replaced tui list with span so we actually know for sure where the buttons are * feature: always display cancel button in kill signal selection * chore: simplified as suggested in review * fix: made scrolling in kill list more intuitive * fix: differentiating macos from linux signals * fix: fixed reversed kill confirmation movement * chore: fixed unused warnings for windows * feature: added G and gg keybindings for kill signal list
This commit is contained in:
parent
49cfc75aca
commit
120da2c85a
234
src/app.rs
234
src/app.rs
@ -1,5 +1,10 @@
|
|||||||
// use std::io::Write;
|
use std::{
|
||||||
use std::{collections::HashMap, path::PathBuf, time::Instant};
|
cmp::{max, min},
|
||||||
|
collections::HashMap,
|
||||||
|
// io::Write,
|
||||||
|
path::PathBuf,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
use unicode_segmentation::GraphemeCursor;
|
use unicode_segmentation::GraphemeCursor;
|
||||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
@ -130,6 +135,13 @@ pub struct App {
|
|||||||
pub config_path: Option<PathBuf>,
|
pub config_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
const MAX_SIGNAL: usize = 1;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const MAX_SIGNAL: usize = 64;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const MAX_SIGNAL: usize = 31;
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
// Reset multi
|
// Reset multi
|
||||||
@ -170,7 +182,8 @@ impl App {
|
|||||||
|
|
||||||
fn close_dd(&mut self) {
|
fn close_dd(&mut self) {
|
||||||
self.delete_dialog_state.is_showing_dd = false;
|
self.delete_dialog_state.is_showing_dd = false;
|
||||||
self.delete_dialog_state.is_on_yes = false;
|
self.delete_dialog_state.selected_signal = KillSignal::default();
|
||||||
|
self.delete_dialog_state.scroll_pos = 0;
|
||||||
self.to_delete_process_list = None;
|
self.to_delete_process_list = None;
|
||||||
self.dd_err = None;
|
self.dd_err = None;
|
||||||
}
|
}
|
||||||
@ -655,12 +668,13 @@ impl App {
|
|||||||
if self.delete_dialog_state.is_showing_dd {
|
if self.delete_dialog_state.is_showing_dd {
|
||||||
if self.dd_err.is_some() {
|
if self.dd_err.is_some() {
|
||||||
self.close_dd();
|
self.close_dd();
|
||||||
} else if self.delete_dialog_state.is_on_yes {
|
} else if self.delete_dialog_state.selected_signal != KillSignal::CANCEL {
|
||||||
// If within dd...
|
// If within dd...
|
||||||
if self.dd_err.is_none() {
|
if self.dd_err.is_none() {
|
||||||
// Also ensure that we didn't just fail a dd...
|
// Also ensure that we didn't just fail a dd...
|
||||||
let dd_result = self.kill_highlighted_process();
|
let dd_result = self.kill_highlighted_process();
|
||||||
self.delete_dialog_state.is_on_yes = false;
|
self.delete_dialog_state.scroll_pos = 0;
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::default();
|
||||||
|
|
||||||
// Check if there was an issue... if so, inform the user.
|
// Check if there was an issue... if so, inform the user.
|
||||||
if let Err(dd_err) = dd_result {
|
if let Err(dd_err) = dd_result {
|
||||||
@ -670,6 +684,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
self.delete_dialog_state.scroll_pos = 0;
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::default();
|
||||||
self.delete_dialog_state.is_showing_dd = false;
|
self.delete_dialog_state.is_showing_dd = false;
|
||||||
}
|
}
|
||||||
self.is_force_redraw = true;
|
self.is_force_redraw = true;
|
||||||
@ -796,12 +812,52 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
pub fn on_number(&mut self, number_char: char) {
|
||||||
|
if self.delete_dialog_state.is_showing_dd {
|
||||||
|
if self
|
||||||
|
.delete_dialog_state
|
||||||
|
.last_number_press
|
||||||
|
.map_or(100, |ins| ins.elapsed().as_millis())
|
||||||
|
> 500
|
||||||
|
{
|
||||||
|
self.delete_dialog_state.keyboard_signal_select = 0;
|
||||||
|
}
|
||||||
|
let mut kbd_signal = self.delete_dialog_state.keyboard_signal_select * 10;
|
||||||
|
kbd_signal += number_char.to_digit(10).unwrap() as usize;
|
||||||
|
if kbd_signal > 64 {
|
||||||
|
kbd_signal %= 100;
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if kbd_signal > 64 || kbd_signal == 32 || kbd_signal == 33 {
|
||||||
|
kbd_signal %= 10;
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
if kbd_signal > 31 {
|
||||||
|
kbd_signal %= 10;
|
||||||
|
}
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::KILL(kbd_signal);
|
||||||
|
if kbd_signal < 10 {
|
||||||
|
self.delete_dialog_state.keyboard_signal_select = kbd_signal;
|
||||||
|
} else {
|
||||||
|
self.delete_dialog_state.keyboard_signal_select = 0;
|
||||||
|
}
|
||||||
|
self.delete_dialog_state.last_number_press = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_up_key(&mut self) {
|
pub fn on_up_key(&mut self) {
|
||||||
if self.is_config_open {
|
if self.is_config_open {
|
||||||
} else if !self.is_in_dialog() {
|
} else if !self.is_in_dialog() {
|
||||||
self.decrement_position_count();
|
self.decrement_position_count();
|
||||||
} else if self.help_dialog_state.is_showing_help {
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
self.help_scroll_up();
|
self.help_scroll_up();
|
||||||
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
self.on_right_key();
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
self.on_left_key();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
}
|
}
|
||||||
@ -812,6 +868,12 @@ impl App {
|
|||||||
self.increment_position_count();
|
self.increment_position_count();
|
||||||
} else if self.help_dialog_state.is_showing_help {
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
self.help_scroll_down();
|
self.help_scroll_down();
|
||||||
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
self.on_left_key();
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
self.on_right_key();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
}
|
}
|
||||||
@ -876,8 +938,25 @@ impl App {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes {
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
self.delete_dialog_state.is_on_yes = true;
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
match self.delete_dialog_state.selected_signal {
|
||||||
|
KillSignal::KILL(prev_signal) => {
|
||||||
|
self.delete_dialog_state.selected_signal = match prev_signal - 1 {
|
||||||
|
0 => KillSignal::CANCEL,
|
||||||
|
// 32+33 are skipped
|
||||||
|
33 => KillSignal::KILL(31),
|
||||||
|
signal => KillSignal::KILL(signal),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
KillSignal::CANCEL => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::KILL(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -947,8 +1026,54 @@ impl App {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes {
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
self.delete_dialog_state.is_on_yes = false;
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
let new_signal = match self.delete_dialog_state.selected_signal {
|
||||||
|
KillSignal::CANCEL => 1,
|
||||||
|
// 32+33 are skipped
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
KillSignal::KILL(31) => 34,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
KillSignal::KILL(31) => 31,
|
||||||
|
KillSignal::KILL(64) => 64,
|
||||||
|
KillSignal::KILL(signal) => signal + 1,
|
||||||
|
};
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::KILL(new_signal);
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::CANCEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_page_up(&mut self) {
|
||||||
|
if self.delete_dialog_state.is_showing_dd {
|
||||||
|
let mut new_signal = match self.delete_dialog_state.selected_signal {
|
||||||
|
KillSignal::CANCEL => 0,
|
||||||
|
KillSignal::KILL(signal) => max(signal, 8) - 8,
|
||||||
|
};
|
||||||
|
if new_signal > 23 && new_signal < 33 {
|
||||||
|
new_signal -= 2;
|
||||||
|
}
|
||||||
|
self.delete_dialog_state.selected_signal = match new_signal {
|
||||||
|
0 => KillSignal::CANCEL,
|
||||||
|
sig => KillSignal::KILL(sig),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_page_down(&mut self) {
|
||||||
|
if self.delete_dialog_state.is_showing_dd {
|
||||||
|
let mut new_signal = match self.delete_dialog_state.selected_signal {
|
||||||
|
KillSignal::CANCEL => 8,
|
||||||
|
KillSignal::KILL(signal) => min(signal + 8, MAX_SIGNAL),
|
||||||
|
};
|
||||||
|
if new_signal > 31 && new_signal < 42 {
|
||||||
|
new_signal += 2;
|
||||||
|
}
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::KILL(new_signal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1181,8 +1306,33 @@ impl App {
|
|||||||
}
|
}
|
||||||
} else if self.delete_dialog_state.is_showing_dd {
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
match caught_char {
|
match caught_char {
|
||||||
'h' | 'j' => self.on_left_key(),
|
'h' => self.on_left_key(),
|
||||||
'k' | 'l' => self.on_right_key(),
|
'j' => self.on_down_key(),
|
||||||
|
'k' => self.on_up_key(),
|
||||||
|
'l' => self.on_right_key(),
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
|
||||||
|
self.on_number(caught_char)
|
||||||
|
}
|
||||||
|
'u' => self.on_page_up(),
|
||||||
|
'd' => self.on_page_down(),
|
||||||
|
'g' => {
|
||||||
|
let mut is_first_g = true;
|
||||||
|
if let Some(second_char) = self.second_char {
|
||||||
|
if self.awaiting_second_char && second_char == 'g' {
|
||||||
|
is_first_g = false;
|
||||||
|
self.awaiting_second_char = false;
|
||||||
|
self.second_char = None;
|
||||||
|
self.skip_to_first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_first_g {
|
||||||
|
self.awaiting_second_char = true;
|
||||||
|
self.second_char = Some('g');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'G' => self.skip_to_last(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if self.is_config_open {
|
} else if self.is_config_open {
|
||||||
@ -1431,10 +1581,22 @@ impl App {
|
|||||||
pub fn kill_highlighted_process(&mut self) -> Result<()> {
|
pub fn kill_highlighted_process(&mut self) -> Result<()> {
|
||||||
if let BottomWidgetType::Proc = self.current_widget.widget_type {
|
if let BottomWidgetType::Proc = self.current_widget.widget_type {
|
||||||
if let Some(current_selected_processes) = &self.to_delete_process_list {
|
if let Some(current_selected_processes) = &self.to_delete_process_list {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let signal = match self.delete_dialog_state.selected_signal {
|
||||||
|
KillSignal::KILL(sig) => sig,
|
||||||
|
KillSignal::CANCEL => 15, // should never happen, so just TERM
|
||||||
|
};
|
||||||
for pid in ¤t_selected_processes.1 {
|
for pid in ¤t_selected_processes.1 {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
process_killer::kill_process_given_pid(*pid, signal)?;
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
process_killer::kill_process_given_pid(*pid)?;
|
process_killer::kill_process_given_pid(*pid)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.to_delete_process_list = None;
|
self.to_delete_process_list = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -1977,6 +2139,8 @@ impl App {
|
|||||||
} else if self.is_config_open {
|
} else if self.is_config_open {
|
||||||
} else if self.help_dialog_state.is_showing_help {
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
self.help_dialog_state.scroll_state.current_scroll_index = 0;
|
self.help_dialog_state.scroll_state.current_scroll_index = 0;
|
||||||
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::CANCEL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2058,6 +2222,8 @@ impl App {
|
|||||||
.scroll_state
|
.scroll_state
|
||||||
.max_scroll_index
|
.max_scroll_index
|
||||||
.saturating_sub(1);
|
.saturating_sub(1);
|
||||||
|
} else if self.delete_dialog_state.is_showing_dd {
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::KILL(MAX_SIGNAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2243,6 +2409,13 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_scroll_up(&mut self) {
|
pub fn handle_scroll_up(&mut self) {
|
||||||
|
if self.delete_dialog_state.is_showing_dd {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
self.on_up_key();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if self.help_dialog_state.is_showing_help {
|
if self.help_dialog_state.is_showing_help {
|
||||||
self.help_scroll_up();
|
self.help_scroll_up();
|
||||||
} else if self.current_widget.widget_type.is_widget_graph() {
|
} else if self.current_widget.widget_type.is_widget_graph() {
|
||||||
@ -2253,6 +2426,13 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_scroll_down(&mut self) {
|
pub fn handle_scroll_down(&mut self) {
|
||||||
|
if self.delete_dialog_state.is_showing_dd {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
self.on_down_key();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if self.help_dialog_state.is_showing_help {
|
if self.help_dialog_state.is_showing_help {
|
||||||
self.help_scroll_down();
|
self.help_scroll_down();
|
||||||
} else if self.current_widget.widget_type.is_widget_graph() {
|
} else if self.current_widget.widget_type.is_widget_graph() {
|
||||||
@ -2592,25 +2772,25 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second short circuit --- are we in the dd dialog state? If so, only check yes/no and
|
// Second short circuit --- are we in the dd dialog state? If so, only check yes/no/signals
|
||||||
// bail after.
|
// and bail after.
|
||||||
if self.is_in_dialog() {
|
if self.is_in_dialog() {
|
||||||
if let (
|
match self.delete_dialog_state.button_positions.iter().find(
|
||||||
Some((yes_tlc_x, yes_tlc_y)),
|
|(tl_x, tl_y, br_x, br_y, _idx)| {
|
||||||
Some((yes_brc_x, yes_brc_y)),
|
(x >= *tl_x && y >= *tl_y) && (x <= *br_x && y <= *br_y)
|
||||||
Some((no_tlc_x, no_tlc_y)),
|
},
|
||||||
Some((no_brc_x, no_brc_y)),
|
|
||||||
) = (
|
|
||||||
self.delete_dialog_state.yes_tlc,
|
|
||||||
self.delete_dialog_state.yes_brc,
|
|
||||||
self.delete_dialog_state.no_tlc,
|
|
||||||
self.delete_dialog_state.no_brc,
|
|
||||||
) {
|
) {
|
||||||
if (x >= yes_tlc_x && y >= yes_tlc_y) && (x <= yes_brc_x && y <= yes_brc_y) {
|
Some((_, _, _, _, 0)) => {
|
||||||
self.delete_dialog_state.is_on_yes = true;
|
self.delete_dialog_state.selected_signal = KillSignal::CANCEL
|
||||||
} else if (x >= no_tlc_x && y >= no_tlc_y) && (x <= no_brc_x && y <= no_brc_y) {
|
|
||||||
self.delete_dialog_state.is_on_yes = false;
|
|
||||||
}
|
}
|
||||||
|
Some((_, _, _, _, idx)) => {
|
||||||
|
if *idx > 31 {
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::KILL(*idx + 2)
|
||||||
|
} else {
|
||||||
|
self.delete_dialog_state.selected_signal = KillSignal::KILL(*idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use winapi::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// This file is meant to house (OS specific) implementations on how to kill processes.
|
/// This file is meant to house (OS specific) implementations on how to kill processes.
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
use crate::utils::error::BottomError;
|
use crate::utils::error::BottomError;
|
||||||
use crate::Pid;
|
use crate::Pid;
|
||||||
|
|
||||||
@ -31,12 +32,10 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kills a process, given a PID.
|
/// Kills a process, given a PID, for unix.
|
||||||
pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> {
|
#[cfg(target_family = "unix")]
|
||||||
if cfg!(target_family = "unix") {
|
pub fn kill_process_given_pid(pid: Pid, signal: usize) -> crate::utils::error::Result<()> {
|
||||||
#[cfg(any(target_family = "unix"))]
|
let output = unsafe { libc::kill(pid as i32, signal as i32) };
|
||||||
{
|
|
||||||
let output = unsafe { libc::kill(pid as i32, libc::SIGTERM) };
|
|
||||||
if output != 0 {
|
if output != 0 {
|
||||||
// We had an error...
|
// We had an error...
|
||||||
let err_code = std::io::Error::last_os_error().raw_os_error();
|
let err_code = std::io::Error::last_os_error().raw_os_error();
|
||||||
@ -59,19 +58,17 @@ pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> {
|
|||||||
)))
|
)))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
} else if cfg!(target_family = "windows") {
|
|
||||||
#[cfg(target_family = "windows")]
|
/// Kills a process, given a PID, for windows.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> {
|
||||||
{
|
{
|
||||||
let process = Process::open(pid as DWORD)?;
|
let process = Process::open(pid as DWORD)?;
|
||||||
process.kill()?;
|
process.kill()?;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return Err(BottomError::GenericError(
|
|
||||||
"Sorry, support operating systems outside the main three are not implemented yet!"
|
|
||||||
.to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -40,14 +40,32 @@ pub struct AppScrollWidgetState {
|
|||||||
pub table_state: TableState,
|
pub table_state: TableState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum KillSignal {
|
||||||
|
CANCEL,
|
||||||
|
KILL(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for KillSignal {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
fn default() -> Self {
|
||||||
|
KillSignal::KILL(15)
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn default() -> Self {
|
||||||
|
KillSignal::KILL(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AppDeleteDialogState {
|
pub struct AppDeleteDialogState {
|
||||||
pub is_showing_dd: bool,
|
pub is_showing_dd: bool,
|
||||||
pub is_on_yes: bool, // Defaults to "No"
|
pub selected_signal: KillSignal,
|
||||||
pub yes_tlc: Option<(u16, u16)>,
|
// tl x, tl y, br x, br y
|
||||||
pub yes_brc: Option<(u16, u16)>,
|
pub button_positions: Vec<(u16, u16, u16, u16, usize)>,
|
||||||
pub no_tlc: Option<(u16, u16)>,
|
pub keyboard_signal_select: usize,
|
||||||
pub no_brc: Option<(u16, u16)>,
|
pub last_number_press: Option<Instant>,
|
||||||
|
pub scroll_pos: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppHelpDialogState {
|
pub struct AppHelpDialogState {
|
||||||
|
@ -322,10 +322,7 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// And reset dd_dialog...
|
// And reset dd_dialog...
|
||||||
app_state.delete_dialog_state.yes_tlc = None;
|
app_state.delete_dialog_state.button_positions = vec![];
|
||||||
app_state.delete_dialog_state.yes_brc = None;
|
|
||||||
app_state.delete_dialog_state.no_tlc = None;
|
|
||||||
app_state.delete_dialog_state.no_brc = None;
|
|
||||||
|
|
||||||
// And battery dialog...
|
// And battery dialog...
|
||||||
for battery_widget in app_state.battery_state.widget_states.values_mut() {
|
for battery_widget in app_state.battery_state.widget_states.values_mut() {
|
||||||
@ -373,14 +370,20 @@ impl Painter {
|
|||||||
|
|
||||||
let dd_text = self.get_dd_spans(app_state);
|
let dd_text = self.get_dd_spans(app_state);
|
||||||
|
|
||||||
let (text_width, text_height) = (
|
let text_width = if terminal_width < 100 {
|
||||||
if terminal_width < 100 {
|
|
||||||
terminal_width * 90 / 100
|
terminal_width * 90 / 100
|
||||||
} else {
|
} else {
|
||||||
terminal_width * 50 / 100
|
terminal_width * 50 / 100
|
||||||
},
|
};
|
||||||
7,
|
let text_height;
|
||||||
);
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
text_height = 22;
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
text_height = 7;
|
||||||
|
}
|
||||||
// let (text_width, text_height) = if let Some(dd_text) = &dd_text {
|
// let (text_width, text_height) = if let Some(dd_text) = &dd_text {
|
||||||
// let width = if current_width < 100 {
|
// let width = if current_width < 100 {
|
||||||
// current_width * 90 / 100
|
// current_width * 90 / 100
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
use std::cmp::min;
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
@ -6,7 +8,10 @@ use tui::{
|
|||||||
widgets::{Block, Borders, Paragraph, Wrap},
|
widgets::{Block, Borders, Paragraph, Wrap},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{app::App, canvas::Painter};
|
use crate::{
|
||||||
|
app::{App, KillSignal},
|
||||||
|
canvas::Painter,
|
||||||
|
};
|
||||||
|
|
||||||
const DD_BASE: &str = " Confirm Kill Process ── Esc to close ";
|
const DD_BASE: &str = " Confirm Kill Process ── Esc to close ";
|
||||||
const DD_ERROR_BASE: &str = " Error ── Esc to close ";
|
const DD_ERROR_BASE: &str = " Error ── Esc to close ";
|
||||||
@ -14,6 +19,10 @@ const DD_ERROR_BASE: &str = " Error ── Esc to close ";
|
|||||||
pub trait KillDialog {
|
pub trait KillDialog {
|
||||||
fn get_dd_spans(&self, app_state: &App) -> Option<Text<'_>>;
|
fn get_dd_spans(&self, app_state: &App) -> Option<Text<'_>>;
|
||||||
|
|
||||||
|
fn draw_dd_confirm_buttons<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, button_draw_loc: &Rect, app_state: &mut App,
|
||||||
|
);
|
||||||
|
|
||||||
fn draw_dd_dialog<B: Backend>(
|
fn draw_dd_dialog<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Text<'_>>, app_state: &mut App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, dd_text: Option<Text<'_>>, app_state: &mut App, draw_loc: Rect,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
@ -58,6 +67,246 @@ impl KillDialog for Painter {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn draw_dd_confirm_buttons<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, button_draw_loc: &Rect, app_state: &mut App,
|
||||||
|
) {
|
||||||
|
let (yes_button, no_button) = match app_state.delete_dialog_state.selected_signal {
|
||||||
|
KillSignal::KILL(_) => (
|
||||||
|
Span::styled("Yes", self.colours.currently_selected_text_style),
|
||||||
|
Span::raw("No"),
|
||||||
|
),
|
||||||
|
KillSignal::CANCEL => (
|
||||||
|
Span::raw("Yes"),
|
||||||
|
Span::styled("No", self.colours.currently_selected_text_style),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let button_layout = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Percentage(35),
|
||||||
|
Constraint::Percentage(30),
|
||||||
|
Constraint::Percentage(35),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(*button_draw_loc);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(yes_button)
|
||||||
|
.block(Block::default())
|
||||||
|
.alignment(Alignment::Right),
|
||||||
|
button_layout[0],
|
||||||
|
);
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(no_button)
|
||||||
|
.block(Block::default())
|
||||||
|
.alignment(Alignment::Left),
|
||||||
|
button_layout[2],
|
||||||
|
);
|
||||||
|
|
||||||
|
if app_state.should_get_widget_bounds() {
|
||||||
|
app_state.delete_dialog_state.button_positions = vec![
|
||||||
|
(
|
||||||
|
button_layout[2].x,
|
||||||
|
button_layout[2].y,
|
||||||
|
button_layout[2].x + button_layout[2].width,
|
||||||
|
button_layout[2].y + button_layout[2].height,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
button_layout[0].x,
|
||||||
|
button_layout[0].y,
|
||||||
|
button_layout[0].x + button_layout[0].width,
|
||||||
|
button_layout[0].y + button_layout[0].height,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
fn draw_dd_confirm_buttons<B: Backend>(
|
||||||
|
&self, f: &mut Frame<'_, B>, button_draw_loc: &Rect, app_state: &mut App,
|
||||||
|
) {
|
||||||
|
let signal_text;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
signal_text = vec![
|
||||||
|
"0: Cancel",
|
||||||
|
"1: HUP",
|
||||||
|
"2: INT",
|
||||||
|
"3: QUIT",
|
||||||
|
"4: ILL",
|
||||||
|
"5: TRAP",
|
||||||
|
"6: ABRT",
|
||||||
|
"7: BUS",
|
||||||
|
"8: FPE",
|
||||||
|
"9: KILL",
|
||||||
|
"10: USR1",
|
||||||
|
"11: SEGV",
|
||||||
|
"12: USR2",
|
||||||
|
"13: PIPE",
|
||||||
|
"14: ALRM",
|
||||||
|
"15: TERM",
|
||||||
|
"16: STKFLT",
|
||||||
|
"17: CHLD",
|
||||||
|
"18: CONT",
|
||||||
|
"19: STOP",
|
||||||
|
"20: TSTP",
|
||||||
|
"21: TTIN",
|
||||||
|
"22: TTOU",
|
||||||
|
"23: URG",
|
||||||
|
"24: XCPU",
|
||||||
|
"25: XFSZ",
|
||||||
|
"26: VTALRM",
|
||||||
|
"27: PROF",
|
||||||
|
"28: WINCH",
|
||||||
|
"29: IO",
|
||||||
|
"30: PWR",
|
||||||
|
"31: SYS",
|
||||||
|
"34: RTMIN",
|
||||||
|
"35: RTMIN+1",
|
||||||
|
"36: RTMIN+2",
|
||||||
|
"37: RTMIN+3",
|
||||||
|
"38: RTMIN+4",
|
||||||
|
"39: RTMIN+5",
|
||||||
|
"40: RTMIN+6",
|
||||||
|
"41: RTMIN+7",
|
||||||
|
"42: RTMIN+8",
|
||||||
|
"43: RTMIN+9",
|
||||||
|
"44: RTMIN+10",
|
||||||
|
"45: RTMIN+11",
|
||||||
|
"46: RTMIN+12",
|
||||||
|
"47: RTMIN+13",
|
||||||
|
"48: RTMIN+14",
|
||||||
|
"49: RTMIN+15",
|
||||||
|
"50: RTMAX-14",
|
||||||
|
"51: RTMAX-13",
|
||||||
|
"52: RTMAX-12",
|
||||||
|
"53: RTMAX-11",
|
||||||
|
"54: RTMAX-10",
|
||||||
|
"55: RTMAX-9",
|
||||||
|
"56: RTMAX-8",
|
||||||
|
"57: RTMAX-7",
|
||||||
|
"58: RTMAX-6",
|
||||||
|
"59: RTMAX-5",
|
||||||
|
"60: RTMAX-4",
|
||||||
|
"61: RTMAX-3",
|
||||||
|
"62: RTMAX-2",
|
||||||
|
"63: RTMAX-1",
|
||||||
|
"64: RTMAX",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
signal_text = vec![
|
||||||
|
"0: Cancel",
|
||||||
|
"1: HUP",
|
||||||
|
"2: INT",
|
||||||
|
"3: QUIT",
|
||||||
|
"4: ILL",
|
||||||
|
"5: TRAP",
|
||||||
|
"6: ABRT",
|
||||||
|
"7: EMT",
|
||||||
|
"8: FPE",
|
||||||
|
"9: KILL",
|
||||||
|
"10: BUS",
|
||||||
|
"11: SEGV",
|
||||||
|
"12: SYS",
|
||||||
|
"13: PIPE",
|
||||||
|
"14: ALRM",
|
||||||
|
"15: TERM",
|
||||||
|
"16: URG",
|
||||||
|
"17: STOP",
|
||||||
|
"18: TSTP",
|
||||||
|
"19: CONT",
|
||||||
|
"20: CHLD",
|
||||||
|
"21: TTIN",
|
||||||
|
"22: TTOU",
|
||||||
|
"23: IO",
|
||||||
|
"24: XCPU",
|
||||||
|
"25: XFSZ",
|
||||||
|
"26: VTALRM",
|
||||||
|
"27: PROF",
|
||||||
|
"28: WINCH",
|
||||||
|
"29: INFO",
|
||||||
|
"30: USR1",
|
||||||
|
"31: USR2",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
let button_rect = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.margin(1)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length((button_draw_loc.width - 14) / 2),
|
||||||
|
Constraint::Min(0),
|
||||||
|
Constraint::Length((button_draw_loc.width - 14) / 2),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(*button_draw_loc)[1];
|
||||||
|
|
||||||
|
let mut selected = match app_state.delete_dialog_state.selected_signal {
|
||||||
|
KillSignal::CANCEL => 0,
|
||||||
|
KillSignal::KILL(signal) => signal,
|
||||||
|
};
|
||||||
|
// 32+33 are skipped
|
||||||
|
if selected > 31 {
|
||||||
|
selected -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(vec![Constraint::Min(1); button_rect.height as usize])
|
||||||
|
.split(button_rect);
|
||||||
|
|
||||||
|
let prev_offset: usize = app_state.delete_dialog_state.scroll_pos;
|
||||||
|
app_state.delete_dialog_state.scroll_pos = if selected == 0 {
|
||||||
|
0
|
||||||
|
} else if selected < prev_offset + 1 {
|
||||||
|
selected - 1
|
||||||
|
} else if selected > prev_offset + (layout.len() as usize) - 1 {
|
||||||
|
selected - (layout.len() as usize) + 1
|
||||||
|
} else {
|
||||||
|
prev_offset
|
||||||
|
};
|
||||||
|
let scroll_offset: usize = app_state.delete_dialog_state.scroll_pos;
|
||||||
|
|
||||||
|
let mut buttons = signal_text
|
||||||
|
[scroll_offset + 1..min((layout.len() as usize) + scroll_offset, signal_text.len())]
|
||||||
|
.iter()
|
||||||
|
.map(|text| Span::raw(*text))
|
||||||
|
.collect::<Vec<Span<'_>>>();
|
||||||
|
buttons.insert(0, Span::raw(signal_text[0]));
|
||||||
|
buttons[selected - scroll_offset] = Span::styled(
|
||||||
|
signal_text[selected],
|
||||||
|
self.colours.currently_selected_text_style,
|
||||||
|
);
|
||||||
|
|
||||||
|
app_state.delete_dialog_state.button_positions = layout
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, pos)| {
|
||||||
|
(
|
||||||
|
pos.x,
|
||||||
|
pos.y,
|
||||||
|
pos.x + pos.width - 1,
|
||||||
|
pos.y + pos.height - 1,
|
||||||
|
if i == 0 { 0 } else { scroll_offset } + i,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(u16, u16, u16, u16, usize)>>();
|
||||||
|
|
||||||
|
for (btn, pos) in buttons.into_iter().zip(layout.into_iter()) {
|
||||||
|
f.render_widget(Paragraph::new(btn).alignment(Alignment::Left), pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_dd_dialog<B: Backend>(
|
fn draw_dd_dialog<B: Backend>(
|
||||||
&self, f: &mut Frame<'_, B>, dd_text: Option<Text<'_>>, app_state: &mut App, draw_loc: Rect,
|
&self, f: &mut Frame<'_, B>, dd_text: Option<Text<'_>>, app_state: &mut App, draw_loc: Rect,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@ -107,67 +356,31 @@ impl KillDialog for Painter {
|
|||||||
draw_loc,
|
draw_loc,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let btn_height;
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
btn_height = 20;
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
btn_height = 3;
|
||||||
|
}
|
||||||
// Now draw buttons if needed...
|
// Now draw buttons if needed...
|
||||||
let split_draw_loc = Layout::default()
|
let split_draw_loc = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(if app_state.dd_err.is_some() {
|
.constraints(
|
||||||
|
if app_state.dd_err.is_some() {
|
||||||
vec![Constraint::Percentage(100)]
|
vec![Constraint::Percentage(100)]
|
||||||
} else {
|
} else {
|
||||||
vec![Constraint::Min(0), Constraint::Length(3)]
|
vec![Constraint::Min(3), Constraint::Length(btn_height)]
|
||||||
})
|
}
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
.split(draw_loc);
|
.split(draw_loc);
|
||||||
|
|
||||||
// This being true implies that dd_err is none.
|
// This being true implies that dd_err is none.
|
||||||
if let Some(button_draw_loc) = split_draw_loc.get(1) {
|
if let Some(button_draw_loc) = split_draw_loc.get(1) {
|
||||||
let (yes_button, no_button) = if app_state.delete_dialog_state.is_on_yes {
|
self.draw_dd_confirm_buttons(f, button_draw_loc, app_state);
|
||||||
(
|
|
||||||
Span::styled("Yes", self.colours.currently_selected_text_style),
|
|
||||||
Span::raw("No"),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
Span::raw("Yes"),
|
|
||||||
Span::styled("No", self.colours.currently_selected_text_style),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let button_layout = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([
|
|
||||||
Constraint::Percentage(35),
|
|
||||||
Constraint::Percentage(30),
|
|
||||||
Constraint::Percentage(35),
|
|
||||||
])
|
|
||||||
.split(*button_draw_loc);
|
|
||||||
|
|
||||||
f.render_widget(
|
|
||||||
Paragraph::new(yes_button)
|
|
||||||
.block(Block::default())
|
|
||||||
.alignment(Alignment::Right),
|
|
||||||
button_layout[0],
|
|
||||||
);
|
|
||||||
f.render_widget(
|
|
||||||
Paragraph::new(no_button)
|
|
||||||
.block(Block::default())
|
|
||||||
.alignment(Alignment::Left),
|
|
||||||
button_layout[2],
|
|
||||||
);
|
|
||||||
|
|
||||||
if app_state.should_get_widget_bounds() {
|
|
||||||
app_state.delete_dialog_state.yes_tlc =
|
|
||||||
Some((button_layout[0].x, button_layout[0].y));
|
|
||||||
app_state.delete_dialog_state.yes_brc = Some((
|
|
||||||
button_layout[0].x + button_layout[0].width,
|
|
||||||
button_layout[0].y + button_layout[0].height,
|
|
||||||
));
|
|
||||||
|
|
||||||
app_state.delete_dialog_state.no_tlc =
|
|
||||||
Some((button_layout[2].x, button_layout[2].y));
|
|
||||||
app_state.delete_dialog_state.no_brc = Some((
|
|
||||||
button_layout[2].x + button_layout[2].width,
|
|
||||||
button_layout[2].y + button_layout[2].height,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_state.dd_err.is_some() {
|
if app_state.dd_err.is_some() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user