mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-23 05:34:57 +02:00
refactor: update how we render and handle process kill dialogs (#1701)
* fmt * temp work * get yes/no working * cleanup * fill out some more buttons * conditional compilation * update * update docs * wait this just works * like 80% of the way there * some stuff around the killing screen * mouse works * done! * clippy * more stuff * fix some imports for windows * android fixes * oop * ahh * hmm
This commit is contained in:
parent
00afd66006
commit
98342617a1
@ -92,7 +92,7 @@ starship-battery = { version = "0.10.1", optional = true }
|
|||||||
sysinfo = "=0.30.13"
|
sysinfo = "=0.30.13"
|
||||||
timeless = "0.0.14-alpha"
|
timeless = "0.0.14-alpha"
|
||||||
toml_edit = { version = "0.22.26", features = ["serde"] }
|
toml_edit = { version = "0.22.26", features = ["serde"] }
|
||||||
tui = { version = "0.29.0", package = "ratatui" }
|
tui = { version = "0.29.0", package = "ratatui", features = ["unstable-rendered-line-info"] }
|
||||||
unicode-ellipsis = "0.3.0"
|
unicode-ellipsis = "0.3.0"
|
||||||
unicode-segmentation = "1.12.0"
|
unicode-segmentation = "1.12.0"
|
||||||
unicode-width = "0.2.0"
|
unicode-width = "0.2.0"
|
||||||
@ -221,7 +221,6 @@ depends = "libc6:armhf (>= 2.28)"
|
|||||||
[package.metadata.wix]
|
[package.metadata.wix]
|
||||||
output = "bottom_x86_64_installer.msi"
|
output = "bottom_x86_64_installer.msi"
|
||||||
|
|
||||||
|
|
||||||
[package.metadata.generate-rpm]
|
[package.metadata.generate-rpm]
|
||||||
assets = [
|
assets = [
|
||||||
{ source = "target/release/btm", dest = "/usr/bin/", mode = "755" },
|
{ source = "target/release/btm", dest = "/usr/bin/", mode = "755" },
|
||||||
|
@ -25,18 +25,18 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
|
|||||||
|
|
||||||
## Process Options
|
## Process Options
|
||||||
|
|
||||||
| Option | Behaviour |
|
| Option | Behaviour |
|
||||||
| --------------------------- | -------------------------------------------------------------------------------------- |
|
| --------------------------- | --------------------------------------------------------------------------------------------- |
|
||||||
| `-S, --case_sensitive` | Enables case sensitivity by default. |
|
| `-S, --case_sensitive` | Enables case sensitivity by default. |
|
||||||
| `-u, --current_usage` | Calculates process CPU usage as a percentage of current usage rather than total usage. |
|
| `-u, --current_usage` | Calculates process CPU usage as a percentage of current usage rather than total usage. |
|
||||||
| `--disable_advanced_kill` | Hides additional stopping options Unix-like systems. |
|
| `--disable_advanced_kill` | Disable being able to send signals to processes. Only available on Linux, macOS, and FreeBSD. |
|
||||||
| `-g, --group_processes` | Groups processes with the same name by default. No effect if `--tree` is set. |
|
| `-g, --group_processes` | Groups processes with the same name by default. No effect if `--tree` is set. |
|
||||||
| `--process_memory_as_value` | Defaults to showing process memory usage by value. |
|
| `--process_memory_as_value` | Defaults to showing process memory usage by value. |
|
||||||
| `--process_command` | Shows the full command name instead of the process name by default. |
|
| `--process_command` | Shows the full command name instead of the process name by default. |
|
||||||
| `-R, --regex` | Enables regex by default while searching. |
|
| `-R, --regex` | Enables regex by default while searching. |
|
||||||
| `-T, --tree` | Makes the process widget use tree mode by default. |
|
| `-T, --tree` | Makes the process widget use tree mode by default. |
|
||||||
| `-n, --unnormalized_cpu` | Show process CPU% usage without averaging over the number of CPU cores. |
|
| `-n, --unnormalized_cpu` | Show process CPU% usage without averaging over the number of CPU cores. |
|
||||||
| `-W, --whole_word` | Enables whole-word matching by default while searching. |
|
| `-W, --whole_word` | Enables whole-word matching by default while searching. |
|
||||||
|
|
||||||
## Temperature Options
|
## Temperature Options
|
||||||
|
|
||||||
|
@ -14,40 +14,40 @@ hide_avg_cpu = true
|
|||||||
Most of the [command line flags](../command-line-options.md) have config file equivalents to avoid having to type them out
|
Most of the [command line flags](../command-line-options.md) have config file equivalents to avoid having to type them out
|
||||||
each time:
|
each time:
|
||||||
|
|
||||||
| Field | Type | Functionality |
|
| Field | Type | Functionality |
|
||||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
|
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `hide_avg_cpu` | Boolean | Hides the average CPU usage. |
|
| `hide_avg_cpu` | Boolean | Hides the average CPU usage. |
|
||||||
| `dot_marker` | Boolean | Uses a dot marker for graphs. |
|
| `dot_marker` | Boolean | Uses a dot marker for graphs. |
|
||||||
| `cpu_left_legend` | Boolean | Puts the CPU chart legend to the left side. |
|
| `cpu_left_legend` | Boolean | Puts the CPU chart legend to the left side. |
|
||||||
| `current_usage` | Boolean | Sets process CPU% to be based on current CPU%. |
|
| `current_usage` | Boolean | Sets process CPU% to be based on current CPU%. |
|
||||||
| `group_processes` | Boolean | Groups processes with the same name by default. |
|
| `group_processes` | Boolean | Groups processes with the same name by default. |
|
||||||
| `case_sensitive` | Boolean | Enables case sensitivity by default. |
|
| `case_sensitive` | Boolean | Enables case sensitivity by default. |
|
||||||
| `whole_word` | Boolean | Enables whole-word matching by default. |
|
| `whole_word` | Boolean | Enables whole-word matching by default. |
|
||||||
| `regex` | Boolean | Enables regex by default. |
|
| `regex` | Boolean | Enables regex by default. |
|
||||||
| `basic` | Boolean | Hides graphs and uses a more basic look. |
|
| `basic` | Boolean | Hides graphs and uses a more basic look. |
|
||||||
| `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. |
|
| `use_old_network_legend` | Boolean | DEPRECATED - uses the older network legend. |
|
||||||
| `battery` | Boolean | Shows the battery widget. |
|
| `battery` | Boolean | Shows the battery widget. |
|
||||||
| `rate` | Unsigned Int (represents milliseconds) or String (represents human time) | Sets a refresh rate in ms. |
|
| `rate` | Unsigned Int (represents milliseconds) or String (represents human time) | Sets a refresh rate in ms. |
|
||||||
| `default_time_value` | Unsigned Int (represents milliseconds) or String (represents human time) | Default time value for graphs in ms. |
|
| `default_time_value` | Unsigned Int (represents milliseconds) or String (represents human time) | Default time value for graphs in ms. |
|
||||||
| `time_delta` | Unsigned Int (represents milliseconds) or String (represents human time) | The amount in ms changed upon zooming. |
|
| `time_delta` | Unsigned Int (represents milliseconds) or String (represents human time) | The amount in ms changed upon zooming. |
|
||||||
| `hide_time` | Boolean | Hides the time scale. |
|
| `hide_time` | Boolean | Hides the time scale. |
|
||||||
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | Sets the temperature unit type. |
|
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) | Sets the temperature unit type. |
|
||||||
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. |
|
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) | Sets the default widget type, use --help for more info. |
|
||||||
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | Sets the n'th selected widget type as the default. |
|
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) | Sets the n'th selected widget type as the default. |
|
||||||
| `disable_click` | Boolean | Disables mouse clicks. |
|
| `disable_click` | Boolean | Disables mouse clicks. |
|
||||||
| `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). |
|
| `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). |
|
||||||
| `process_memory_as_value` | Boolean | Defaults to showing process memory usage by value. |
|
| `process_memory_as_value` | Boolean | Defaults to showing process memory usage by value. |
|
||||||
| `tree` | Boolean | Defaults to showing the process widget in tree mode. |
|
| `tree` | Boolean | Defaults to showing the process widget in tree mode. |
|
||||||
| `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. |
|
| `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. |
|
||||||
| `process_command` | Boolean | Show processes as their commands by default. |
|
| `process_command` | Boolean | Show processes as their commands by default. |
|
||||||
| `disable_advanced_kill` | Boolean | Hides advanced options to stop a process on Unix-like systems. |
|
| `disable_advanced_kill` | Boolean | Disable being able to send signals to processes on supported Unix-like systems. Only available on Linux, macOS, and FreeBSD. |
|
||||||
| `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. |
|
| `network_use_binary_prefix` | Boolean | Displays the network widget with binary prefixes. |
|
||||||
| `network_use_bytes` | Boolean | Displays the network widget using bytes. |
|
| `network_use_bytes` | Boolean | Displays the network widget using bytes. |
|
||||||
| `network_use_log` | Boolean | Displays the network widget with a log scale. |
|
| `network_use_log` | Boolean | Displays the network widget with a log scale. |
|
||||||
| `disable_gpu` | Boolean | Disable NVIDIA and AMD GPU data collection. |
|
| `disable_gpu` | Boolean | Disable NVIDIA and AMD GPU data collection. |
|
||||||
| `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. |
|
| `retention` | String (human readable time, such as "10m", "1h", etc.) | How much data is stored at once in terms of time. |
|
||||||
| `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. |
|
| `unnormalized_cpu` | Boolean | Show process CPU% without normalizing over the number of cores. |
|
||||||
| `expanded` | Boolean | Expand the default widget upon starting the app. |
|
| `expanded` | Boolean | Expand the default widget upon starting the app. |
|
||||||
| `memory_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the memory widget. |
|
| `memory_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the memory widget. |
|
||||||
| `network_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the network widget. |
|
| `network_legend` | String (one of ["none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right"]) | Where to place the legend for the network widget. |
|
||||||
| `average_cpu_row` | Boolean | Moves the average CPU usage entry to its own row when using basic mode. |
|
| `average_cpu_row` | Boolean | Moves the average CPU usage entry to its own row when using basic mode. |
|
||||||
|
@ -83,8 +83,8 @@ operating systems, you are also able to control which specific signals to send (
|
|||||||
<figcaption><sub>The process termination menu on Linux</sub></figcaption>
|
<figcaption><sub>The process termination menu on Linux</sub></figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
If you're on Windows, or if the `disable_advanced_kill` flag is set in the options or command-line, then a simpler termination
|
If you're on Windows, or if the `disable_advanced_kill` flag is set in the options or command-line (only available on
|
||||||
screen will be shown to confirm whether you want to kill that process/process group.
|
Linux, macOS, and FreeBSD), then a simpler termination screen with just yes or no options will be shown.
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<img src="../../../assets/screenshots/process/process_kill_simple.webp" alt="A picture of the process kill menu on Windows."/>
|
<img src="../../../assets/screenshots/process/process_kill_simple.webp" alt="A picture of the process kill menu on Windows."/>
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
# Displays the network widget with a log scale.
|
# Displays the network widget with a log scale.
|
||||||
#network_use_log = false
|
#network_use_log = false
|
||||||
|
|
||||||
# Hides advanced options to stop a process on Unix-like systems.
|
# Hides advanced options to stop a process on Unix-like systems. Only available on Linux, macOS, and FreeBSD
|
||||||
#disable_advanced_kill = false
|
#disable_advanced_kill = false
|
||||||
|
|
||||||
# Hide GPU(s) information
|
# Hide GPU(s) information
|
||||||
@ -115,20 +115,17 @@
|
|||||||
# Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
|
# Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
|
||||||
#network_legend = "top-right"
|
#network_legend = "top-right"
|
||||||
|
|
||||||
|
|
||||||
# Processes widget configuration
|
# Processes widget configuration
|
||||||
#[processes]
|
#[processes]
|
||||||
# The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built):
|
# The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built):
|
||||||
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
|
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
|
||||||
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"]
|
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"]
|
||||||
|
|
||||||
|
|
||||||
# CPU widget configuration
|
# CPU widget configuration
|
||||||
#[cpu]
|
#[cpu]
|
||||||
# One of "all" (default), "average"/"avg"
|
# One of "all" (default), "average"/"avg"
|
||||||
#default = "average"
|
#default = "average"
|
||||||
|
|
||||||
|
|
||||||
# Disk widget configuration
|
# Disk widget configuration
|
||||||
#[disk]
|
#[disk]
|
||||||
# The columns shown by the process widget. The following columns are supported:
|
# The columns shown by the process widget. The following columns are supported:
|
||||||
@ -170,7 +167,6 @@
|
|||||||
# Whether to be require matching the whole word. Defaults to false.
|
# Whether to be require matching the whole word. Defaults to false.
|
||||||
#whole_word = false
|
#whole_word = false
|
||||||
|
|
||||||
|
|
||||||
# Temperature widget configuration
|
# Temperature widget configuration
|
||||||
#[temperature]
|
#[temperature]
|
||||||
# By default, there are no temperature sensor filters enabled. An example use case is provided below.
|
# By default, there are no temperature sensor filters enabled. An example use case is provided below.
|
||||||
@ -190,7 +186,6 @@
|
|||||||
# Whether to be require matching the whole word. Defaults to false.
|
# Whether to be require matching the whole word. Defaults to false.
|
||||||
#whole_word = false
|
#whole_word = false
|
||||||
|
|
||||||
|
|
||||||
# Network widget configuration
|
# Network widget configuration
|
||||||
#[network]
|
#[network]
|
||||||
# By default, there are no network interface filters enabled. An example use case is provided below.
|
# By default, there are no network interface filters enabled. An example use case is provided below.
|
||||||
@ -210,7 +205,6 @@
|
|||||||
# Whether to be require matching the whole word. Defaults to false.
|
# Whether to be require matching the whole word. Defaults to false.
|
||||||
#whole_word = false
|
#whole_word = false
|
||||||
|
|
||||||
|
|
||||||
# These are all the components that support custom theming. Note that colour support
|
# These are all the components that support custom theming. Note that colour support
|
||||||
# will depend on terminal support.
|
# will depend on terminal support.
|
||||||
#[styles] # Uncomment if you want to use custom styling
|
#[styles] # Uncomment if you want to use custom styling
|
||||||
|
419
src/app.rs
419
src/app.rs
@ -1,15 +1,10 @@
|
|||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod layout_manager;
|
pub mod layout_manager;
|
||||||
mod process_killer;
|
|
||||||
pub mod states;
|
pub mod states;
|
||||||
|
|
||||||
use std::{
|
use std::time::Instant;
|
||||||
cmp::{max, min},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use concat_string::concat_string;
|
use concat_string::concat_string;
|
||||||
use data::*;
|
use data::*;
|
||||||
use filter::*;
|
use filter::*;
|
||||||
@ -18,9 +13,9 @@ use layout_manager::*;
|
|||||||
pub use states::*;
|
pub use states::*;
|
||||||
use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
|
use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
|
||||||
|
|
||||||
|
use crate::canvas::dialogs::process_kill_dialog::ProcessKillDialog;
|
||||||
use crate::{
|
use crate::{
|
||||||
canvas::components::time_graph::LegendPosition,
|
canvas::components::time_graph::LegendPosition,
|
||||||
collection::processes::Pid,
|
|
||||||
constants,
|
constants,
|
||||||
utils::data_units::DataUnit,
|
utils::data_units::DataUnit,
|
||||||
widgets::{ProcWidgetColumn, ProcWidgetMode},
|
widgets::{ProcWidgetColumn, ProcWidgetMode},
|
||||||
@ -57,6 +52,7 @@ pub struct AppConfigFields {
|
|||||||
pub enable_gpu: bool,
|
pub enable_gpu: bool,
|
||||||
pub enable_cache_memory: bool,
|
pub enable_cache_memory: bool,
|
||||||
pub show_table_scroll_position: bool,
|
pub show_table_scroll_position: bool,
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
pub is_advanced_kill: bool,
|
pub is_advanced_kill: bool,
|
||||||
pub memory_legend_position: Option<LegendPosition>,
|
pub memory_legend_position: Option<LegendPosition>,
|
||||||
// TODO: Remove these, move network details state-side.
|
// TODO: Remove these, move network details state-side.
|
||||||
@ -77,35 +73,12 @@ pub struct DataFilters {
|
|||||||
pub net_filter: Option<Filter>,
|
pub net_filter: Option<Filter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(target_os = "linux")] {
|
|
||||||
/// The max signal we can send to a process on Linux.
|
|
||||||
pub const MAX_PROCESS_SIGNAL: usize = 64;
|
|
||||||
} else if #[cfg(target_os = "macos")] {
|
|
||||||
/// The max signal we can send to a process on macOS.
|
|
||||||
pub const MAX_PROCESS_SIGNAL: usize = 31;
|
|
||||||
} else if #[cfg(target_os = "freebsd")] {
|
|
||||||
/// The max signal we can send to a process on FreeBSD.
|
|
||||||
/// See [https://www.freebsd.org/cgi/man.cgi?query=signal&apropos=0&sektion=3&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html]
|
|
||||||
/// for more details.
|
|
||||||
pub const MAX_PROCESS_SIGNAL: usize = 33;
|
|
||||||
} else if #[cfg(target_os = "windows")] {
|
|
||||||
/// The max signal we can send to a process. For Windows, we only have support for one signal (kill).
|
|
||||||
pub const MAX_PROCESS_SIGNAL: usize = 1;
|
|
||||||
} else {
|
|
||||||
/// The max signal we can send to a process. As a fallback, we only support one signal (kill).
|
|
||||||
pub const MAX_PROCESS_SIGNAL: usize = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
awaiting_second_char: bool,
|
awaiting_second_char: bool,
|
||||||
second_char: Option<char>,
|
second_char: Option<char>,
|
||||||
pub dd_err: Option<String>, // FIXME: The way we do deletes is really gross.
|
|
||||||
to_delete_process_list: Option<(String, Vec<Pid>)>,
|
|
||||||
pub data_store: DataStore,
|
pub data_store: DataStore,
|
||||||
last_key_press: Instant,
|
last_key_press: Instant,
|
||||||
pub delete_dialog_state: AppDeleteDialogState,
|
pub(crate) process_kill_dialog: ProcessKillDialog,
|
||||||
pub help_dialog_state: AppHelpDialogState,
|
pub help_dialog_state: AppHelpDialogState,
|
||||||
pub is_expanded: bool,
|
pub is_expanded: bool,
|
||||||
pub is_force_redraw: bool,
|
pub is_force_redraw: bool,
|
||||||
@ -129,11 +102,9 @@ impl App {
|
|||||||
Self {
|
Self {
|
||||||
awaiting_second_char: false,
|
awaiting_second_char: false,
|
||||||
second_char: None,
|
second_char: None,
|
||||||
dd_err: None,
|
|
||||||
to_delete_process_list: None,
|
|
||||||
data_store: DataStore::default(),
|
data_store: DataStore::default(),
|
||||||
last_key_press: Instant::now(),
|
last_key_press: Instant::now(),
|
||||||
delete_dialog_state: AppDeleteDialogState::default(),
|
process_kill_dialog: ProcessKillDialog::default(),
|
||||||
help_dialog_state: AppHelpDialogState::default(),
|
help_dialog_state: AppHelpDialogState::default(),
|
||||||
is_expanded,
|
is_expanded,
|
||||||
is_force_redraw: false,
|
is_force_redraw: false,
|
||||||
@ -186,7 +157,7 @@ impl App {
|
|||||||
|
|
||||||
// Reset dialog state
|
// Reset dialog state
|
||||||
self.help_dialog_state.is_showing_help = false;
|
self.help_dialog_state.is_showing_help = false;
|
||||||
self.delete_dialog_state.is_showing_dd = false;
|
self.process_kill_dialog.reset();
|
||||||
|
|
||||||
// Close all searches and reset it
|
// Close all searches and reset it
|
||||||
self.states
|
self.states
|
||||||
@ -197,10 +168,6 @@ impl App {
|
|||||||
state.proc_search.search_state.reset();
|
state.proc_search.search_state.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear current delete list
|
|
||||||
self.to_delete_process_list = None;
|
|
||||||
self.dd_err = None;
|
|
||||||
|
|
||||||
self.data_store.reset();
|
self.data_store.reset();
|
||||||
|
|
||||||
// Reset zoom
|
// Reset zoom
|
||||||
@ -213,24 +180,15 @@ impl App {
|
|||||||
self.is_force_redraw || self.is_determining_widget_boundary
|
self.is_force_redraw || self.is_determining_widget_boundary
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_dd(&mut self) {
|
|
||||||
self.delete_dialog_state.is_showing_dd = false;
|
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::default();
|
|
||||||
self.delete_dialog_state.scroll_pos = 0;
|
|
||||||
self.to_delete_process_list = None;
|
|
||||||
self.dd_err = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_esc(&mut self) {
|
pub fn on_esc(&mut self) {
|
||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
if self.is_in_dialog() {
|
|
||||||
if self.help_dialog_state.is_showing_help {
|
|
||||||
self.help_dialog_state.is_showing_help = false;
|
|
||||||
self.help_dialog_state.scroll_state.current_scroll_index = 0;
|
|
||||||
} else {
|
|
||||||
self.close_dd();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if self.process_kill_dialog.is_open() {
|
||||||
|
self.process_kill_dialog.on_esc();
|
||||||
|
self.is_force_redraw = true;
|
||||||
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
|
self.help_dialog_state.is_showing_help = false;
|
||||||
|
self.help_dialog_state.scroll_state.current_scroll_index = 0;
|
||||||
self.is_force_redraw = true;
|
self.is_force_redraw = true;
|
||||||
} else {
|
} else {
|
||||||
match self.current_widget.widget_type {
|
match self.current_widget.widget_type {
|
||||||
@ -299,7 +257,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_in_dialog(&self) -> bool {
|
fn is_in_dialog(&self) -> bool {
|
||||||
self.help_dialog_state.is_showing_help || self.delete_dialog_state.is_showing_dd
|
self.help_dialog_state.is_showing_help || self.process_kill_dialog.is_open()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ignore_normal_keybinds(&self) -> bool {
|
fn ignore_normal_keybinds(&self) -> bool {
|
||||||
@ -477,30 +435,9 @@ impl App {
|
|||||||
|
|
||||||
/// One of two functions allowed to run while in a dialog...
|
/// One of two functions allowed to run while in a dialog...
|
||||||
pub fn on_enter(&mut self) {
|
pub fn on_enter(&mut self) {
|
||||||
if self.delete_dialog_state.is_showing_dd {
|
if self.process_kill_dialog.is_open() {
|
||||||
if self.dd_err.is_some() {
|
// Not the best way of doing things for now but works as glue.
|
||||||
self.close_dd();
|
self.process_kill_dialog.on_enter();
|
||||||
} else if self.delete_dialog_state.selected_signal != KillSignal::Cancel {
|
|
||||||
// If within dd...
|
|
||||||
if self.dd_err.is_none() {
|
|
||||||
// Also ensure that we didn't just fail a dd...
|
|
||||||
let dd_result = self.kill_highlighted_process();
|
|
||||||
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.
|
|
||||||
if let Err(dd_err) = dd_result {
|
|
||||||
self.dd_err = Some(dd_err.to_string());
|
|
||||||
} else {
|
|
||||||
self.delete_dialog_state.is_showing_dd = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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.is_force_redraw = true;
|
|
||||||
} else if !self.is_in_dialog() {
|
} else if !self.is_in_dialog() {
|
||||||
if let BottomWidgetType::ProcSort = self.current_widget.widget_type {
|
if let BottomWidgetType::ProcSort = self.current_widget.widget_type {
|
||||||
if let Some(proc_widget_state) = self
|
if let Some(proc_widget_state) = self
|
||||||
@ -561,7 +498,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomWidgetType::Proc => {
|
BottomWidgetType::Proc => {
|
||||||
self.start_killing_process();
|
self.kill_current_process();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -610,80 +547,28 @@ 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())
|
|
||||||
>= 400
|
|
||||||
{
|
|
||||||
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_in_dialog() {
|
if !self.is_in_dialog() {
|
||||||
self.decrement_position_count();
|
self.decrement_position_count();
|
||||||
|
self.reset_multi_tap_keys();
|
||||||
} 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 {
|
self.reset_multi_tap_keys();
|
||||||
#[cfg(target_os = "windows")]
|
} else if self.process_kill_dialog.is_open() {
|
||||||
self.on_right_key();
|
self.process_kill_dialog.on_up_key();
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
if self.app_config_fields.is_advanced_kill {
|
|
||||||
self.on_left_key();
|
|
||||||
} else {
|
|
||||||
self.on_right_key();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
self.reset_multi_tap_keys();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_down_key(&mut self) {
|
pub fn on_down_key(&mut self) {
|
||||||
if !self.is_in_dialog() {
|
if !self.is_in_dialog() {
|
||||||
self.increment_position_count();
|
self.increment_position_count();
|
||||||
|
self.reset_multi_tap_keys();
|
||||||
} 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 {
|
self.reset_multi_tap_keys();
|
||||||
#[cfg(target_os = "windows")]
|
} else if self.process_kill_dialog.is_open() {
|
||||||
self.on_left_key();
|
self.process_kill_dialog.on_down_key();
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
if self.app_config_fields.is_advanced_kill {
|
|
||||||
self.on_right_key();
|
|
||||||
} else {
|
|
||||||
self.on_left_key();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
self.reset_multi_tap_keys();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_left_key(&mut self) {
|
pub fn on_left_key(&mut self) {
|
||||||
@ -731,29 +616,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if self.delete_dialog_state.is_showing_dd {
|
} else if self.process_kill_dialog.is_open() {
|
||||||
#[cfg(target_family = "unix")]
|
self.process_kill_dialog.on_left_key();
|
||||||
{
|
|
||||||
if self.app_config_fields.is_advanced_kill {
|
|
||||||
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 => {}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Kill(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -807,45 +671,14 @@ impl App {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if self.delete_dialog_state.is_showing_dd {
|
} else if self.process_kill_dialog.is_open() {
|
||||||
#[cfg(target_family = "unix")]
|
self.process_kill_dialog.on_right_key();
|
||||||
{
|
|
||||||
if self.app_config_fields.is_advanced_kill {
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Cancel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Cancel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_page_up(&mut self) {
|
pub fn on_page_up(&mut self) {
|
||||||
if self.delete_dialog_state.is_showing_dd {
|
if self.process_kill_dialog.is_open() {
|
||||||
let mut new_signal = match self.delete_dialog_state.selected_signal {
|
self.process_kill_dialog.on_page_up();
|
||||||
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),
|
|
||||||
};
|
|
||||||
} else if self.help_dialog_state.is_showing_help {
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
let current = &mut self.help_dialog_state.scroll_state.current_scroll_index;
|
let current = &mut self.help_dialog_state.scroll_state.current_scroll_index;
|
||||||
let amount = self.help_dialog_state.height;
|
let amount = self.help_dialog_state.height;
|
||||||
@ -864,15 +697,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_page_down(&mut self) {
|
pub fn on_page_down(&mut self) {
|
||||||
if self.delete_dialog_state.is_showing_dd {
|
if self.process_kill_dialog.is_open() {
|
||||||
let mut new_signal = match self.delete_dialog_state.selected_signal {
|
self.process_kill_dialog.on_page_down();
|
||||||
KillSignal::Cancel => 8,
|
|
||||||
KillSignal::Kill(signal) => min(signal + 8, MAX_PROCESS_SIGNAL),
|
|
||||||
};
|
|
||||||
if new_signal > 31 && new_signal < 42 {
|
|
||||||
new_signal += 2;
|
|
||||||
}
|
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Kill(new_signal);
|
|
||||||
} else if self.help_dialog_state.is_showing_help {
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
let current = self.help_dialog_state.scroll_state.current_scroll_index;
|
let current = self.help_dialog_state.scroll_state.current_scroll_index;
|
||||||
let amount = self.help_dialog_state.height;
|
let amount = self.help_dialog_state.height;
|
||||||
@ -1059,34 +885,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_killing_process(&mut self) {
|
|
||||||
self.reset_multi_tap_keys();
|
|
||||||
|
|
||||||
if let Some(pws) = self
|
|
||||||
.states
|
|
||||||
.proc_state
|
|
||||||
.widget_states
|
|
||||||
.get(&self.current_widget.widget_id)
|
|
||||||
{
|
|
||||||
if let Some(current) = pws.table.current_item() {
|
|
||||||
let id = current.id.to_string();
|
|
||||||
if let Some(pids) = pws
|
|
||||||
.id_pid_map
|
|
||||||
.get(&id)
|
|
||||||
.cloned()
|
|
||||||
.or_else(|| Some(vec![current.pid]))
|
|
||||||
{
|
|
||||||
let current_process = (id, pids);
|
|
||||||
|
|
||||||
self.to_delete_process_list = Some(current_process);
|
|
||||||
self.delete_dialog_state.is_showing_dd = true;
|
|
||||||
self.is_determining_widget_boundary = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// FIXME: This should handle errors.
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_char_key(&mut self, caught_char: char) {
|
pub fn on_char_key(&mut self, caught_char: char) {
|
||||||
// Skip control code chars
|
// Skip control code chars
|
||||||
if caught_char.is_control() {
|
if caught_char.is_control() {
|
||||||
@ -1159,34 +957,50 @@ impl App {
|
|||||||
'j' | 'k' | 'g' | 'G' => self.handle_char(caught_char),
|
'j' | 'k' | 'g' | 'G' => self.handle_char(caught_char),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if self.delete_dialog_state.is_showing_dd {
|
} else if self.process_kill_dialog.is_open() {
|
||||||
match caught_char {
|
self.process_kill_dialog.on_char(caught_char);
|
||||||
'h' => self.on_left_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)
|
|
||||||
}
|
|
||||||
'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 {
|
/// Kill the currently selected process if we are in the process widget.
|
||||||
self.awaiting_second_char = true;
|
///
|
||||||
self.second_char = Some('g');
|
/// TODO: This ideally gets abstracted out into a separate widget.
|
||||||
}
|
pub(crate) fn kill_current_process(&mut self) {
|
||||||
|
if let Some(pws) = self
|
||||||
|
.states
|
||||||
|
.proc_state
|
||||||
|
.widget_states
|
||||||
|
.get(&self.current_widget.widget_id)
|
||||||
|
{
|
||||||
|
if let Some(current) = pws.table.current_item() {
|
||||||
|
let id = current.id.to_string();
|
||||||
|
if let Some(pids) = pws
|
||||||
|
.id_pid_map
|
||||||
|
.get(&id)
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| Some(vec![current.pid]))
|
||||||
|
{
|
||||||
|
let current_process = (id, pids);
|
||||||
|
|
||||||
|
let use_simple_selection = {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] {
|
||||||
|
!self.app_config_fields.is_advanced_kill
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.process_kill_dialog.start_process_kill(
|
||||||
|
current_process.0,
|
||||||
|
current_process.1,
|
||||||
|
use_simple_selection,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: I don't think most of this is needed.
|
||||||
|
self.is_determining_widget_boundary = true;
|
||||||
}
|
}
|
||||||
'G' => self.skip_to_last(),
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1206,7 +1020,9 @@ impl App {
|
|||||||
self.awaiting_second_char = false;
|
self.awaiting_second_char = false;
|
||||||
self.second_char = None;
|
self.second_char = None;
|
||||||
|
|
||||||
self.start_killing_process();
|
self.reset_multi_tap_keys();
|
||||||
|
|
||||||
|
self.kill_current_process();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1421,36 +1237,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kill_highlighted_process(&mut self) -> anyhow::Result<()> {
|
|
||||||
if let BottomWidgetType::Proc = self.current_widget.widget_type {
|
|
||||||
if let Some((_, pids)) = &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 pids {
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
process_killer::kill_process_given_pid(*pid, signal)?;
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
process_killer::kill_process_given_pid(*pid)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.to_delete_process_list = None;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!("Cannot kill processes if the current widget is not the Process widget!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_to_delete_processes(&self) -> Option<(String, Vec<Pid>)> {
|
|
||||||
self.to_delete_process_list.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_expand_widget(&mut self) {
|
fn toggle_expand_widget(&mut self) {
|
||||||
if self.is_expanded {
|
if self.is_expanded {
|
||||||
self.is_expanded = false;
|
self.is_expanded = false;
|
||||||
@ -1989,8 +1775,8 @@ impl App {
|
|||||||
self.reset_multi_tap_keys();
|
self.reset_multi_tap_keys();
|
||||||
} 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 {
|
} else if self.process_kill_dialog.is_open() {
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Cancel;
|
self.process_kill_dialog.go_to_first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2050,8 +1836,8 @@ impl App {
|
|||||||
} 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 =
|
self.help_dialog_state.scroll_state.current_scroll_index =
|
||||||
self.help_dialog_state.scroll_state.max_scroll_index;
|
self.help_dialog_state.scroll_state.max_scroll_index;
|
||||||
} else if self.delete_dialog_state.is_showing_dd {
|
} else if self.process_kill_dialog.is_open() {
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Kill(MAX_PROCESS_SIGNAL);
|
self.process_kill_dialog.go_to_last();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2160,14 +1946,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_scroll_up(&mut self) {
|
pub fn handle_scroll_up(&mut self) {
|
||||||
if self.delete_dialog_state.is_showing_dd {
|
if self.process_kill_dialog.is_open() {
|
||||||
#[cfg(target_family = "unix")]
|
self.process_kill_dialog.on_scroll_up();
|
||||||
{
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
self.on_up_key();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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() {
|
||||||
self.zoom_in();
|
self.zoom_in();
|
||||||
@ -2177,14 +1958,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_scroll_down(&mut self) {
|
pub fn handle_scroll_down(&mut self) {
|
||||||
if self.delete_dialog_state.is_showing_dd {
|
if self.process_kill_dialog.is_open() {
|
||||||
#[cfg(target_family = "unix")]
|
self.process_kill_dialog.on_scroll_down();
|
||||||
{
|
} else if self.help_dialog_state.is_showing_help {
|
||||||
self.on_down_key();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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() {
|
||||||
self.zoom_out();
|
self.zoom_out();
|
||||||
@ -2516,24 +2292,7 @@ impl App {
|
|||||||
|
|
||||||
// Second short circuit --- are we in the dd dialog state? If so, only check
|
// Second short circuit --- are we in the dd dialog state? If so, only check
|
||||||
// yes/no/signals and bail after.
|
// yes/no/signals and bail after.
|
||||||
if self.is_in_dialog() {
|
if self.process_kill_dialog.is_open() && self.process_kill_dialog.on_click(x, y) {
|
||||||
match self.delete_dialog_state.button_positions.iter().find(
|
|
||||||
|(tl_x, tl_y, br_x, br_y, _idx)| {
|
|
||||||
(x >= *tl_x && y >= *tl_y) && (x <= *br_x && y <= *br_y)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Some((_, _, _, _, 0)) => {
|
|
||||||
self.delete_dialog_state.selected_signal = KillSignal::Cancel
|
|
||||||
}
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,12 +184,12 @@ impl TimeSeriesData {
|
|||||||
partition_point - 1
|
partition_point - 1
|
||||||
} else {
|
} else {
|
||||||
// If the partition point was 0, then it means all values are too new to be pruned.
|
// If the partition point was 0, then it means all values are too new to be pruned.
|
||||||
crate::info!("Skipping prune.");
|
// crate::info!("Skipping prune.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::info!("Pruning up to index {end}.");
|
// crate::info!("Pruning up to index {end}.");
|
||||||
|
|
||||||
// Note that end here is _inclusive_.
|
// Note that end here is _inclusive_.
|
||||||
self.time.drain(0..=end);
|
self.time.drain(0..=end);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{ops::Range, time::Instant};
|
use std::ops::Range;
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
@ -31,34 +31,6 @@ pub enum CursorDirection {
|
|||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
|
||||||
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)]
|
|
||||||
pub struct AppDeleteDialogState {
|
|
||||||
pub is_showing_dd: bool,
|
|
||||||
pub selected_signal: KillSignal,
|
|
||||||
/// tl x, tl y, br x, br y, index/signal
|
|
||||||
pub button_positions: Vec<(u16, u16, u16, u16, usize)>,
|
|
||||||
pub keyboard_signal_select: usize,
|
|
||||||
pub last_number_press: Option<Instant>,
|
|
||||||
pub scroll_pos: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AppHelpDialogState {
|
pub struct AppHelpDialogState {
|
||||||
pub is_showing_help: bool,
|
pub is_showing_help: bool,
|
||||||
pub height: u16,
|
pub height: u16,
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
//! or components.
|
//! or components.
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
mod dialogs;
|
pub mod dialogs;
|
||||||
mod drawing_utils;
|
mod drawing_utils;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
@ -200,6 +200,7 @@ impl Painter {
|
|||||||
self.previous_width = terminal_width;
|
self.previous_width = terminal_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We should probably remove this or make it done elsewhere, not the responsibility of the app.
|
||||||
if app_state.should_get_widget_bounds() {
|
if app_state.should_get_widget_bounds() {
|
||||||
// If we're force drawing, reset ALL mouse boundaries.
|
// If we're force drawing, reset ALL mouse boundaries.
|
||||||
for widget in app_state.widget_map.values_mut() {
|
for widget in app_state.widget_map.values_mut() {
|
||||||
@ -207,15 +208,16 @@ impl Painter {
|
|||||||
widget.bottom_right_corner = None;
|
widget.bottom_right_corner = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset dd_dialog...
|
// Reset process kill dialog button locations...
|
||||||
app_state.delete_dialog_state.button_positions = vec![];
|
app_state.process_kill_dialog.handle_redraw();
|
||||||
|
|
||||||
// Reset battery dialog...
|
// Reset battery dialog button locations...
|
||||||
for battery_widget in app_state.states.battery_state.widget_states.values_mut() {
|
for battery_widget in app_state.states.battery_state.widget_states.values_mut() {
|
||||||
battery_widget.tab_click_locs = None;
|
battery_widget.tab_click_locs = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make drawing dialog generic.
|
||||||
if app_state.help_dialog_state.is_showing_help {
|
if app_state.help_dialog_state.is_showing_help {
|
||||||
let gen_help_len = GENERAL_HELP_TEXT.len() as u16 + 3;
|
let gen_help_len = GENERAL_HELP_TEXT.len() as u16 + 3;
|
||||||
let border_len = terminal_height.saturating_sub(gen_help_len) / 2;
|
let border_len = terminal_height.saturating_sub(gen_help_len) / 2;
|
||||||
@ -248,46 +250,32 @@ impl Painter {
|
|||||||
.split(vertical_dialog_chunk[1]);
|
.split(vertical_dialog_chunk[1]);
|
||||||
|
|
||||||
self.draw_help_dialog(f, app_state, middle_dialog_chunk[1]);
|
self.draw_help_dialog(f, app_state, middle_dialog_chunk[1]);
|
||||||
} else if app_state.delete_dialog_state.is_showing_dd {
|
} else if app_state.process_kill_dialog.is_open() {
|
||||||
let dd_text = self.get_dd_spans(app_state);
|
// FIXME: For width, just limit to a max size or full width. For height, not sure. Maybe pass max and let child handle?
|
||||||
|
let horizontal_padding = if terminal_width < 100 { 0 } else { 5 };
|
||||||
|
let vertical_padding = if terminal_height < 100 { 0 } else { 5 };
|
||||||
|
|
||||||
let text_width = if terminal_width < 100 {
|
|
||||||
terminal_width * 90 / 100
|
|
||||||
} else {
|
|
||||||
terminal_width * 50 / 100
|
|
||||||
};
|
|
||||||
|
|
||||||
let text_height = if cfg!(target_os = "windows")
|
|
||||||
|| !app_state.app_config_fields.is_advanced_kill
|
|
||||||
{
|
|
||||||
7
|
|
||||||
} else {
|
|
||||||
22
|
|
||||||
};
|
|
||||||
|
|
||||||
let vertical_bordering = terminal_height.saturating_sub(text_height) / 2;
|
|
||||||
let vertical_dialog_chunk = Layout::default()
|
let vertical_dialog_chunk = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Length(vertical_bordering),
|
Constraint::Length(vertical_padding),
|
||||||
Constraint::Length(text_height),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(vertical_bordering),
|
Constraint::Length(vertical_padding),
|
||||||
])
|
])
|
||||||
.split(terminal_size);
|
.areas::<3>(terminal_size)[1];
|
||||||
|
|
||||||
let horizontal_bordering = terminal_width.saturating_sub(text_width) / 2;
|
let dialog_draw_area = Layout::default()
|
||||||
let middle_dialog_chunk = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Length(horizontal_bordering),
|
Constraint::Length(horizontal_padding),
|
||||||
Constraint::Length(text_width),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(horizontal_bordering),
|
Constraint::Length(horizontal_padding),
|
||||||
])
|
])
|
||||||
.split(vertical_dialog_chunk[1]);
|
.areas::<3>(vertical_dialog_chunk)[1];
|
||||||
|
|
||||||
// This is a bit nasty, but it works well... I guess.
|
app_state
|
||||||
app_state.delete_dialog_state.is_showing_dd =
|
.process_kill_dialog
|
||||||
self.draw_dd_dialog(f, dd_text, app_state, middle_dialog_chunk[1]);
|
.draw(f, dialog_draw_area, &self.styles);
|
||||||
} else if app_state.is_expanded {
|
} else if app_state.is_expanded {
|
||||||
if let Some(frozen_draw_loc) = frozen_draw_loc {
|
if let Some(frozen_draw_loc) = frozen_draw_loc {
|
||||||
self.draw_frozen_indicator(f, frozen_draw_loc);
|
self.draw_frozen_indicator(f, frozen_draw_loc);
|
||||||
|
@ -1,418 +0,0 @@
|
|||||||
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "macos"))]
|
|
||||||
use std::cmp::min;
|
|
||||||
|
|
||||||
use tui::{
|
|
||||||
Frame,
|
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
|
||||||
text::{Line, Span, Text},
|
|
||||||
widgets::{Block, Paragraph, Wrap},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
app::{App, KillSignal, MAX_PROCESS_SIGNAL},
|
|
||||||
canvas::{Painter, drawing_utils::dialog_block},
|
|
||||||
widgets::ProcWidgetMode,
|
|
||||||
};
|
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(target_os = "linux")] {
|
|
||||||
const SIGNAL_TEXT: [&str; 63] = [
|
|
||||||
"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",
|
|
||||||
];
|
|
||||||
} else if #[cfg(target_os = "macos")] {
|
|
||||||
const SIGNAL_TEXT: [&str; 32] = [
|
|
||||||
"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",
|
|
||||||
];
|
|
||||||
} else if #[cfg(target_os = "freebsd")] {
|
|
||||||
const SIGNAL_TEXT: [&str; 34] = [
|
|
||||||
"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",
|
|
||||||
"32: THR",
|
|
||||||
"33: LIBRT",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Painter {
|
|
||||||
pub fn get_dd_spans(&self, app_state: &App) -> Option<Text<'_>> {
|
|
||||||
if let Some(dd_err) = &app_state.dd_err {
|
|
||||||
return Some(Text::from(vec![
|
|
||||||
Line::default(),
|
|
||||||
Line::from("Failed to kill process."),
|
|
||||||
Line::from(dd_err.clone()),
|
|
||||||
Line::from("Please press ENTER or ESC to close this dialog."),
|
|
||||||
]));
|
|
||||||
} else if let Some(to_kill_processes) = app_state.get_to_delete_processes() {
|
|
||||||
if let Some(first_pid) = to_kill_processes.1.first() {
|
|
||||||
return Some(Text::from(vec![
|
|
||||||
Line::from(""),
|
|
||||||
if app_state
|
|
||||||
.states
|
|
||||||
.proc_state
|
|
||||||
.widget_states
|
|
||||||
.get(&app_state.current_widget.widget_id)
|
|
||||||
.map(|p| matches!(p.mode, ProcWidgetMode::Grouped))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
if to_kill_processes.1.len() != 1 {
|
|
||||||
Line::from(format!(
|
|
||||||
"Kill {} processes with the name '{}'? Press ENTER to confirm.",
|
|
||||||
to_kill_processes.1.len(),
|
|
||||||
to_kill_processes.0
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Line::from(format!(
|
|
||||||
"Kill 1 process with the name '{}'? Press ENTER to confirm.",
|
|
||||||
to_kill_processes.0
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Line::from(format!(
|
|
||||||
"Kill process '{}' with PID {}? Press ENTER to confirm.",
|
|
||||||
to_kill_processes.0, first_pid
|
|
||||||
))
|
|
||||||
},
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_dd_confirm_buttons(
|
|
||||||
&self, f: &mut Frame<'_>, button_draw_loc: &Rect, app_state: &mut App,
|
|
||||||
) {
|
|
||||||
if MAX_PROCESS_SIGNAL == 1 || !app_state.app_config_fields.is_advanced_kill {
|
|
||||||
let (yes_button, no_button) = match app_state.delete_dialog_state.selected_signal {
|
|
||||||
KillSignal::Kill(_) => (
|
|
||||||
Span::styled("Yes", self.styles.selected_text_style),
|
|
||||||
Span::styled("No", self.styles.text_style),
|
|
||||||
),
|
|
||||||
KillSignal::Cancel => (
|
|
||||||
Span::styled("Yes", self.styles.text_style),
|
|
||||||
Span::styled("No", self.styles.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() {
|
|
||||||
const SIGNAL: usize = if cfg!(target_os = "windows") { 1 } else { 15 };
|
|
||||||
|
|
||||||
// This is kinda weird, but the gist is:
|
|
||||||
// - We have three sections; we put our mouse bounding box for the "yes" button
|
|
||||||
// at the very right edge of the left section and 3 characters back. We then
|
|
||||||
// give it a buffer size of 1 on the x-coordinate.
|
|
||||||
// - Same for the "no" button, except it is the right section and we do it from
|
|
||||||
// the start of the right section.
|
|
||||||
//
|
|
||||||
// Lastly, note that mouse detection for the dd buttons assume correct widths.
|
|
||||||
// As such, we correct them here and check with >= and <= mouse
|
|
||||||
// bound checks, as opposed to how we do it elsewhere with >= and <. See https://github.com/ClementTsang/bottom/pull/459 for details.
|
|
||||||
app_state.delete_dialog_state.button_positions = vec![
|
|
||||||
// Yes
|
|
||||||
(
|
|
||||||
button_layout[0].x + button_layout[0].width - 4,
|
|
||||||
button_layout[0].y,
|
|
||||||
button_layout[0].x + button_layout[0].width,
|
|
||||||
button_layout[0].y,
|
|
||||||
SIGNAL,
|
|
||||||
),
|
|
||||||
// No
|
|
||||||
(
|
|
||||||
button_layout[2].x - 1,
|
|
||||||
button_layout[2].y,
|
|
||||||
button_layout[2].x + 2,
|
|
||||||
button_layout[2].y,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "macos"))]
|
|
||||||
{
|
|
||||||
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.into()])
|
|
||||||
.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() - 1 {
|
|
||||||
selected - layout.len() + 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()) + scroll_offset, SIGNAL_TEXT.len())]
|
|
||||||
.iter()
|
|
||||||
.map(|text| Span::styled(*text, self.styles.text_style))
|
|
||||||
.collect::<Vec<Span<'_>>>();
|
|
||||||
buttons.insert(0, Span::styled(SIGNAL_TEXT[0], self.styles.text_style));
|
|
||||||
buttons[selected - scroll_offset] =
|
|
||||||
Span::styled(SIGNAL_TEXT[selected], self.styles.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.iter()) {
|
|
||||||
f.render_widget(Paragraph::new(btn).alignment(Alignment::Left), *pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_dd_dialog(
|
|
||||||
&self, f: &mut Frame<'_>, dd_text: Option<Text<'_>>, app_state: &mut App, draw_loc: Rect,
|
|
||||||
) -> bool {
|
|
||||||
if let Some(dd_text) = dd_text {
|
|
||||||
let dd_title = if app_state.dd_err.is_some() {
|
|
||||||
Line::styled(" Error ", self.styles.widget_title_style)
|
|
||||||
} else {
|
|
||||||
Line::styled(" Confirm Kill Process ", self.styles.widget_title_style)
|
|
||||||
};
|
|
||||||
|
|
||||||
f.render_widget(
|
|
||||||
Paragraph::new(dd_text)
|
|
||||||
.block(
|
|
||||||
dialog_block(self.styles.border_type)
|
|
||||||
.title_top(dd_title)
|
|
||||||
.title_top(
|
|
||||||
Line::styled(" Esc to close ", self.styles.widget_title_style)
|
|
||||||
.right_aligned(),
|
|
||||||
)
|
|
||||||
.style(self.styles.border_style)
|
|
||||||
.border_style(self.styles.border_style),
|
|
||||||
)
|
|
||||||
.style(self.styles.text_style)
|
|
||||||
.alignment(Alignment::Center)
|
|
||||||
.wrap(Wrap { trim: true }),
|
|
||||||
draw_loc,
|
|
||||||
);
|
|
||||||
|
|
||||||
let btn_height = {
|
|
||||||
cfg_if::cfg_if! {
|
|
||||||
if #[cfg(target_os = "windows")] {
|
|
||||||
3
|
|
||||||
} else {
|
|
||||||
if !app_state.app_config_fields.is_advanced_kill {
|
|
||||||
3
|
|
||||||
} else {
|
|
||||||
20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Now draw buttons if needed...
|
|
||||||
let split_draw_loc = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints(if app_state.dd_err.is_some() {
|
|
||||||
vec![Constraint::Percentage(100)]
|
|
||||||
} else {
|
|
||||||
vec![Constraint::Min(3), Constraint::Length(btn_height)]
|
|
||||||
})
|
|
||||||
.split(draw_loc);
|
|
||||||
|
|
||||||
// This being true implies that dd_err is none.
|
|
||||||
if let Some(button_draw_loc) = split_draw_loc.get(1) {
|
|
||||||
self.draw_dd_confirm_buttons(f, button_draw_loc, app_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if app_state.dd_err.is_some() {
|
|
||||||
return app_state.delete_dialog_state.is_showing_dd;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Currently we just return "false" if things go wrong finding
|
|
||||||
// the process or a first PID (if an error arises it should be caught).
|
|
||||||
// I don't really like this, and I find it ugly, but it works for now.
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +1,2 @@
|
|||||||
pub mod dd_dialog;
|
|
||||||
pub mod help_dialog;
|
pub mod help_dialog;
|
||||||
|
pub mod process_kill_dialog;
|
||||||
|
826
src/canvas/dialogs/process_kill_dialog.rs
Normal file
826
src/canvas/dialogs/process_kill_dialog.rs
Normal file
@ -0,0 +1,826 @@
|
|||||||
|
//! A dialog box to handle killing processes.
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use tui::{
|
||||||
|
Frame,
|
||||||
|
layout::{Alignment, Constraint, Flex, Layout, Position, Rect},
|
||||||
|
text::{Line, Span, Text},
|
||||||
|
widgets::{Paragraph, Wrap},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
use tui::widgets::ListState;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
canvas::drawing_utils::dialog_block, collection::processes::Pid, options::config::style::Styles,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Configure signal text based on the target OS.
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(target_os = "linux")] {
|
||||||
|
const DEFAULT_KILL_SIGNAL: usize = 15;
|
||||||
|
const SIGNAL_TEXT: [&str; 63] = [
|
||||||
|
"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",
|
||||||
|
];
|
||||||
|
} else if #[cfg(target_os = "macos")] {
|
||||||
|
const DEFAULT_KILL_SIGNAL: usize = 15;
|
||||||
|
const SIGNAL_TEXT: [&str; 32] = [
|
||||||
|
"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",
|
||||||
|
];
|
||||||
|
} else if #[cfg(target_os = "freebsd")] {
|
||||||
|
const DEFAULT_KILL_SIGNAL: usize = 15;
|
||||||
|
const SIGNAL_TEXT: [&str; 34] = [
|
||||||
|
"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",
|
||||||
|
"32: THR",
|
||||||
|
"33: LIBRT",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Button state type for a [`ProcessKillDialog`].
|
||||||
|
///
|
||||||
|
/// Simple only has two buttons (yes/no), while signals (AKA advanced) are
|
||||||
|
/// a list of signals to send.
|
||||||
|
///
|
||||||
|
/// Note that signals are not available for Windows.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum ButtonState {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
Signals {
|
||||||
|
state: ListState,
|
||||||
|
last_button_draw_area: Rect,
|
||||||
|
},
|
||||||
|
Simple {
|
||||||
|
yes: bool,
|
||||||
|
last_yes_button_area: Rect,
|
||||||
|
last_no_button_area: Rect,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ProcessKillSelectingInner {
|
||||||
|
process_name: String,
|
||||||
|
pids: Vec<Pid>,
|
||||||
|
button_state: ButtonState,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The current state of the process kill dialog.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
enum ProcessKillDialogState {
|
||||||
|
#[default]
|
||||||
|
NotEnabled,
|
||||||
|
Selecting(ProcessKillSelectingInner),
|
||||||
|
Error {
|
||||||
|
process_name: String,
|
||||||
|
pid: Option<Pid>,
|
||||||
|
err: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process kill dialog.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub(crate) struct ProcessKillDialog {
|
||||||
|
state: ProcessKillDialogState,
|
||||||
|
last_char: Option<char>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessKillDialog {
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
*self = Self::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
!(matches!(self.state, ProcessKillDialogState::NotEnabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_esc(&mut self) {
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_enter(&mut self) {
|
||||||
|
// We do this to get around borrow issues.
|
||||||
|
let mut current = ProcessKillDialogState::NotEnabled;
|
||||||
|
std::mem::swap(&mut self.state, &mut current);
|
||||||
|
|
||||||
|
if let ProcessKillDialogState::Selecting(state) = current {
|
||||||
|
let process_name = state.process_name;
|
||||||
|
let button_state = state.button_state;
|
||||||
|
let pids = state.pids;
|
||||||
|
|
||||||
|
match button_state {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
ButtonState::Signals { state, .. } => {
|
||||||
|
use crate::utils::process_killer;
|
||||||
|
|
||||||
|
if let Some(signal) = state.selected() {
|
||||||
|
if signal != 0 {
|
||||||
|
for pid in pids {
|
||||||
|
if let Err(err) =
|
||||||
|
process_killer::kill_process_given_pid(pid, signal)
|
||||||
|
{
|
||||||
|
self.state = ProcessKillDialogState::Error {
|
||||||
|
process_name,
|
||||||
|
pid: Some(pid),
|
||||||
|
err: err.to_string(),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ButtonState::Simple { yes, .. } => {
|
||||||
|
if yes {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(target_os = "windows")] {
|
||||||
|
use crate::utils::process_killer;
|
||||||
|
|
||||||
|
for pid in pids {
|
||||||
|
if let Err(err) = process_killer::kill_process_given_pid(pid) {
|
||||||
|
self.state = ProcessKillDialogState::Error { process_name, pid: Some(pid), err: err.to_string() };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] {
|
||||||
|
use crate::utils::process_killer;
|
||||||
|
|
||||||
|
for pid in pids {
|
||||||
|
// Send a SIGTERM by default.
|
||||||
|
if let Err(err) = process_killer::kill_process_given_pid(pid, DEFAULT_KILL_SIGNAL) {
|
||||||
|
self.state = ProcessKillDialogState::Error { process_name, pid: Some(pid), err: err.to_string() };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.state = ProcessKillDialogState::Error { process_name, pid: None, err: "Killing processes is not supported on this platform.".into() };
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall through behaviour is just to close the dialog.
|
||||||
|
self.last_char = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_char(&mut self, c: char) {
|
||||||
|
match c {
|
||||||
|
'h' => self.on_left_key(),
|
||||||
|
'j' => self.on_down_key(),
|
||||||
|
'k' => self.on_up_key(),
|
||||||
|
'l' => self.on_right_key(),
|
||||||
|
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let Some(value) = c.to_digit(10) {
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Signals { state, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
let current = state.selected().unwrap_or(0);
|
||||||
|
let new = current * 10 + value as usize;
|
||||||
|
|
||||||
|
// If the new value is too large, then just assume we instead want the value itself.
|
||||||
|
if new >= SIGNAL_TEXT.len() {
|
||||||
|
state.select(Some(value as usize));
|
||||||
|
} else {
|
||||||
|
state.select(Some(new))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'g' => {
|
||||||
|
if let Some('g') = self.last_char {
|
||||||
|
self.go_to_first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'G' => {
|
||||||
|
self.go_to_last();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.last_char = Some(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle a click at the given coordinates. Returns true if the click was
|
||||||
|
/// handled, false otherwise.
|
||||||
|
pub fn on_click(&mut self, x: u16, y: u16) -> bool {
|
||||||
|
if let ProcessKillDialogState::Selecting(state) = &mut self.state {
|
||||||
|
match &mut state.button_state {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
ButtonState::Signals {
|
||||||
|
state,
|
||||||
|
last_button_draw_area,
|
||||||
|
} => {
|
||||||
|
if last_button_draw_area.contains(Position { x, y }) {
|
||||||
|
let relative_y =
|
||||||
|
y.saturating_sub(last_button_draw_area.y) as usize + state.offset();
|
||||||
|
if relative_y < SIGNAL_TEXT.len() {
|
||||||
|
state.select(Some(relative_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ButtonState::Simple {
|
||||||
|
yes,
|
||||||
|
last_yes_button_area,
|
||||||
|
last_no_button_area,
|
||||||
|
} => {
|
||||||
|
if last_yes_button_area.contains(Position { x, y }) {
|
||||||
|
*yes = true;
|
||||||
|
} else if last_no_button_area.contains(Position { x, y }) {
|
||||||
|
*yes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll up in the signal list.
|
||||||
|
pub fn on_scroll_up(&mut self) {
|
||||||
|
self.on_up_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll down in the signal list.
|
||||||
|
pub fn on_scroll_down(&mut self) {
|
||||||
|
self.on_down_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle a left key press.
|
||||||
|
pub fn on_left_key(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Simple { yes, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
*yes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle a right key press.
|
||||||
|
pub fn on_right_key(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Simple { yes, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
*yes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
fn scroll_up_by(state: &mut ListState, amount: usize) {
|
||||||
|
if let Some(selected) = state.selected() {
|
||||||
|
if let Some(new_position) = selected.checked_sub(amount) {
|
||||||
|
state.select(Some(new_position));
|
||||||
|
} else {
|
||||||
|
state.select(Some(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
fn scroll_down_by(state: &mut ListState, amount: usize) {
|
||||||
|
if let Some(selected) = state.selected() {
|
||||||
|
let new_position = selected + amount;
|
||||||
|
if new_position < SIGNAL_TEXT.len() {
|
||||||
|
state.select(Some(new_position));
|
||||||
|
} else {
|
||||||
|
state.select(Some(SIGNAL_TEXT.len() - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle an up key press.
|
||||||
|
pub fn on_up_key(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Signals { state, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
Self::scroll_up_by(state, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle a down key press.
|
||||||
|
pub fn on_down_key(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Signals { state, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
Self::scroll_down_by(state, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle page up.
|
||||||
|
pub fn on_page_up(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state:
|
||||||
|
ButtonState::Signals {
|
||||||
|
state,
|
||||||
|
last_button_draw_area,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
Self::scroll_up_by(state, last_button_draw_area.height as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle page down.
|
||||||
|
pub fn on_page_down(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state:
|
||||||
|
ButtonState::Signals {
|
||||||
|
state,
|
||||||
|
last_button_draw_area,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
Self::scroll_down_by(state, last_button_draw_area.height as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_first(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Signals { state, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
state.select(Some(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_last(&mut self) {
|
||||||
|
self.last_char = None;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Signals { state, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
state.select(Some(SIGNAL_TEXT.len() - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable the process kill process.
|
||||||
|
pub fn start_process_kill(
|
||||||
|
&mut self, process_name: String, pids: Vec<Pid>, use_simple_selection: bool,
|
||||||
|
) {
|
||||||
|
let button_state = if use_simple_selection {
|
||||||
|
ButtonState::Simple {
|
||||||
|
yes: false,
|
||||||
|
last_yes_button_area: Rect::default(),
|
||||||
|
last_no_button_area: Rect::default(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg_if! {
|
||||||
|
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))] {
|
||||||
|
ButtonState::Signals { state: ListState::default().with_selected(Some(DEFAULT_KILL_SIGNAL)), last_button_draw_area: Rect::default() }
|
||||||
|
} else {
|
||||||
|
ButtonState::Simple { yes: false, last_yes_button_area: Rect::default(), last_no_button_area: Rect::default()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if pids.is_empty() {
|
||||||
|
self.state = ProcessKillDialogState::Error {
|
||||||
|
process_name,
|
||||||
|
pid: None,
|
||||||
|
err: "No PIDs found for the given process name.".into(),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
process_name,
|
||||||
|
pids,
|
||||||
|
button_state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_redraw(&mut self) {
|
||||||
|
// FIXME: Not sure if we need this. We can probably handle this better in the draw function later.
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
{
|
||||||
|
if let ProcessKillDialogState::Selecting(ProcessKillSelectingInner {
|
||||||
|
button_state: ButtonState::Signals { state, .. },
|
||||||
|
..
|
||||||
|
}) = &mut self.state
|
||||||
|
{
|
||||||
|
// Fix the button offset state when we do things like resize.
|
||||||
|
*state.offset_mut() = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw_selecting(
|
||||||
|
f: &mut Frame<'_>, draw_area: Rect, styles: &Styles, state: &mut ProcessKillSelectingInner,
|
||||||
|
) {
|
||||||
|
let ProcessKillSelectingInner {
|
||||||
|
process_name,
|
||||||
|
pids,
|
||||||
|
button_state,
|
||||||
|
..
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
// FIXME: Add some colour to this!
|
||||||
|
let text = {
|
||||||
|
const MAX_PROCESS_NAME_WIDTH: usize = 20;
|
||||||
|
|
||||||
|
if let Some(first_pid) = pids.first() {
|
||||||
|
let truncated_process_name =
|
||||||
|
unicode_ellipsis::truncate_str(process_name, MAX_PROCESS_NAME_WIDTH);
|
||||||
|
|
||||||
|
let text = if pids.len() > 1 {
|
||||||
|
Line::from(format!(
|
||||||
|
"Kill {} processes with the name '{}'? Press ENTER to confirm.",
|
||||||
|
pids.len(),
|
||||||
|
truncated_process_name
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Line::from(format!(
|
||||||
|
"Kill process '{truncated_process_name}' with PID {first_pid}? Press ENTER to confirm."
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
Text::from(vec![text])
|
||||||
|
} else {
|
||||||
|
Text::from(vec![
|
||||||
|
"Could not find process to kill.".into(),
|
||||||
|
"Please press ENTER or ESC to close this dialog.".into(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let text: Paragraph<'_> = Paragraph::new(text)
|
||||||
|
.style(styles.text_style)
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
|
||||||
|
let title = match button_state {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
ButtonState::Signals { .. } => {
|
||||||
|
Line::styled(" Select Signal ", styles.widget_title_style)
|
||||||
|
}
|
||||||
|
ButtonState::Simple { .. } => {
|
||||||
|
Line::styled(" Confirm Kill Process ", styles.widget_title_style)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = dialog_block(styles.border_type)
|
||||||
|
.title_top(title)
|
||||||
|
.title_top(Line::styled(" Esc to close ", styles.widget_title_style).right_aligned())
|
||||||
|
.style(styles.border_style)
|
||||||
|
.border_style(styles.border_style);
|
||||||
|
|
||||||
|
let num_lines = text.line_count(block.inner(draw_area).width) as u16;
|
||||||
|
|
||||||
|
match button_state {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
ButtonState::Signals {
|
||||||
|
state,
|
||||||
|
last_button_draw_area,
|
||||||
|
} => {
|
||||||
|
use tui::widgets::List;
|
||||||
|
|
||||||
|
// A list of options, displayed vertically.
|
||||||
|
const SIGNAL_TEXT_LEN: u16 = SIGNAL_TEXT.len() as u16;
|
||||||
|
|
||||||
|
// Make the rect only as big as it needs to be, which is the height of the text,
|
||||||
|
// the buttons, and up to 2 spaces (margin and space between), and the size of the block.
|
||||||
|
let [draw_area] =
|
||||||
|
Layout::vertical([Constraint::Max(num_lines + SIGNAL_TEXT_LEN + 2 + 3)])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(draw_area);
|
||||||
|
|
||||||
|
// Now we need to divide the block into one area for the paragraph,
|
||||||
|
// and one for the buttons.
|
||||||
|
let [text_draw_area, button_draw_area] = Layout::vertical([
|
||||||
|
Constraint::Max(num_lines),
|
||||||
|
Constraint::Max(SIGNAL_TEXT_LEN),
|
||||||
|
])
|
||||||
|
.flex(Flex::SpaceAround)
|
||||||
|
.areas(block.inner(draw_area));
|
||||||
|
|
||||||
|
// Render the block.
|
||||||
|
f.render_widget(block, draw_area);
|
||||||
|
|
||||||
|
// Now render the text.
|
||||||
|
f.render_widget(text, text_draw_area);
|
||||||
|
|
||||||
|
// And the tricky part, rendering the buttons.
|
||||||
|
let selected = state
|
||||||
|
.selected()
|
||||||
|
.expect("the list state should always be initialized with a selection!");
|
||||||
|
|
||||||
|
let buttons = List::new(SIGNAL_TEXT.iter().enumerate().map(|(index, &signal)| {
|
||||||
|
let style = if index == selected {
|
||||||
|
styles.selected_text_style
|
||||||
|
} else {
|
||||||
|
styles.text_style
|
||||||
|
};
|
||||||
|
|
||||||
|
Span::styled(signal, style)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// This is kinda dumb how you have to set the constraint, but ok.
|
||||||
|
const LONGEST_SIGNAL_TEXT_LENGTH: u16 = const {
|
||||||
|
let mut i = 0;
|
||||||
|
let mut max = 0;
|
||||||
|
while i < SIGNAL_TEXT.len() {
|
||||||
|
if SIGNAL_TEXT[i].len() > max {
|
||||||
|
max = SIGNAL_TEXT[i].len();
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
max as u16
|
||||||
|
};
|
||||||
|
let [button_draw_area] =
|
||||||
|
Layout::horizontal([Constraint::Length(LONGEST_SIGNAL_TEXT_LENGTH)])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(button_draw_area);
|
||||||
|
|
||||||
|
*last_button_draw_area = button_draw_area;
|
||||||
|
f.render_stateful_widget(buttons, button_draw_area, state);
|
||||||
|
}
|
||||||
|
ButtonState::Simple {
|
||||||
|
yes,
|
||||||
|
last_yes_button_area,
|
||||||
|
last_no_button_area,
|
||||||
|
} => {
|
||||||
|
// Make the rect only as big as it needs to be, which is the height of the text,
|
||||||
|
// the buttons, and up to 3 spaces (margin and space between) + 2 for block.
|
||||||
|
let [draw_area] = Layout::vertical([Constraint::Max(num_lines + 1 + 3 + 2)])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(draw_area);
|
||||||
|
|
||||||
|
// Now we need to divide the block into one area for the paragraph,
|
||||||
|
// and one for the buttons.
|
||||||
|
let [text_area, button_area] =
|
||||||
|
Layout::vertical([Constraint::Max(num_lines), Constraint::Length(1)])
|
||||||
|
.flex(Flex::SpaceAround)
|
||||||
|
.areas(block.inner(draw_area));
|
||||||
|
|
||||||
|
// Render things, starting from the block.
|
||||||
|
f.render_widget(block, draw_area);
|
||||||
|
f.render_widget(text, text_area);
|
||||||
|
|
||||||
|
let (yes, no) = {
|
||||||
|
let (yes_style, no_style) = if *yes {
|
||||||
|
(styles.selected_text_style, styles.text_style)
|
||||||
|
} else {
|
||||||
|
(styles.text_style, styles.selected_text_style)
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
Paragraph::new(Span::styled("Yes", yes_style)),
|
||||||
|
Paragraph::new(Span::styled("No", no_style)),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let [yes_area, no_area] = Layout::horizontal([Constraint::Length(3); 2])
|
||||||
|
.flex(Flex::SpaceAround)
|
||||||
|
.areas(button_area);
|
||||||
|
|
||||||
|
*last_yes_button_area = yes_area;
|
||||||
|
*last_no_button_area = no_area;
|
||||||
|
|
||||||
|
f.render_widget(yes, yes_area);
|
||||||
|
f.render_widget(no, no_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw_no_button_dialog(
|
||||||
|
&self, f: &mut Frame<'_>, draw_area: Rect, styles: &Styles, text: Text<'_>, title: Line<'_>,
|
||||||
|
) {
|
||||||
|
let text = Paragraph::new(text)
|
||||||
|
.style(styles.text_style)
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
|
||||||
|
let block = dialog_block(styles.border_type)
|
||||||
|
.title_top(title)
|
||||||
|
.title_top(Line::styled(" Esc to close ", styles.widget_title_style).right_aligned())
|
||||||
|
.style(styles.border_style)
|
||||||
|
.border_style(styles.border_style);
|
||||||
|
|
||||||
|
let num_lines = text.line_count(block.inner(draw_area).width) as u16;
|
||||||
|
|
||||||
|
// Also calculate how big of a draw loc we actually need. For this
|
||||||
|
// one, we want it to be shorter if possible.
|
||||||
|
//
|
||||||
|
// Note the +2 is for the margin, and another +2 for border.
|
||||||
|
let [draw_area] = Layout::vertical([Constraint::Max(num_lines + 2 + 2)])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(draw_area);
|
||||||
|
|
||||||
|
let [text_draw_area] = Layout::vertical([Constraint::Length(num_lines)])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(block.inner(draw_area));
|
||||||
|
|
||||||
|
f.render_widget(block, draw_area);
|
||||||
|
f.render_widget(text, text_draw_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the [`ProcessKillDialog`].
|
||||||
|
pub fn draw(&mut self, f: &mut Frame<'_>, draw_area: Rect, styles: &Styles) {
|
||||||
|
// The idea is:
|
||||||
|
// - Use as big of a dialog box as needed (within the maximal draw loc)
|
||||||
|
// - So the non-button ones are going to be smaller... probably
|
||||||
|
// whatever the height of the text is.
|
||||||
|
// - Meanwhile for the button one, it'll likely be full height if it's
|
||||||
|
// "advanced" kill.
|
||||||
|
|
||||||
|
const MAX_DIALOG_WIDTH: u16 = 100;
|
||||||
|
let [draw_area] = Layout::horizontal([Constraint::Max(MAX_DIALOG_WIDTH)])
|
||||||
|
.flex(Flex::Center)
|
||||||
|
.areas(draw_area);
|
||||||
|
|
||||||
|
// FIXME: Add some colour to this!
|
||||||
|
match &mut self.state {
|
||||||
|
ProcessKillDialogState::NotEnabled => {}
|
||||||
|
ProcessKillDialogState::Selecting(state) => {
|
||||||
|
// Draw a text box. If buttons are yes/no, fit it, otherwise, use max space.
|
||||||
|
Self::draw_selecting(f, draw_area, styles, state);
|
||||||
|
}
|
||||||
|
ProcessKillDialogState::Error {
|
||||||
|
process_name,
|
||||||
|
pid,
|
||||||
|
err,
|
||||||
|
} => {
|
||||||
|
let text = Text::from(vec![
|
||||||
|
if let Some(pid) = pid {
|
||||||
|
format!("Failed to kill process {process_name} ({pid}):").into()
|
||||||
|
} else {
|
||||||
|
format!("Failed to kill process '{process_name}':").into()
|
||||||
|
},
|
||||||
|
err.to_owned().into(),
|
||||||
|
"Please press ENTER or ESC to close this dialog.".into(),
|
||||||
|
])
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
let title = Line::styled(" Error ", styles.widget_title_style);
|
||||||
|
|
||||||
|
self.draw_no_button_dialog(f, draw_area, styles, text, title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,7 +78,7 @@ pub fn handle_key_event_or_break(
|
|||||||
KeyCode::F(3) => app.toggle_search_regex(),
|
KeyCode::F(3) => app.toggle_search_regex(),
|
||||||
KeyCode::F(5) => app.toggle_tree_mode(),
|
KeyCode::F(5) => app.toggle_tree_mode(),
|
||||||
KeyCode::F(6) => app.toggle_sort_menu(),
|
KeyCode::F(6) => app.toggle_sort_menu(),
|
||||||
KeyCode::F(9) => app.start_killing_process(),
|
KeyCode::F(9) => app.kill_current_process(),
|
||||||
KeyCode::PageDown => app.on_page_down(),
|
KeyCode::PageDown => app.on_page_down(),
|
||||||
KeyCode::PageUp => app.on_page_up(),
|
KeyCode::PageUp => app.on_page_up(),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -14,6 +14,7 @@ mod utils {
|
|||||||
pub(crate) mod data_units;
|
pub(crate) mod data_units;
|
||||||
pub(crate) mod general;
|
pub(crate) mod general;
|
||||||
pub(crate) mod logging;
|
pub(crate) mod logging;
|
||||||
|
pub(crate) mod process_killer;
|
||||||
pub(crate) mod strings;
|
pub(crate) mod strings;
|
||||||
}
|
}
|
||||||
pub(crate) mod canvas;
|
pub(crate) mod canvas;
|
||||||
|
@ -224,6 +224,7 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
|
|||||||
let is_use_regex = is_flag_enabled!(regex, args.process, config);
|
let is_use_regex = is_flag_enabled!(regex, args.process, config);
|
||||||
let is_default_tree = is_flag_enabled!(tree, args.process, config);
|
let is_default_tree = is_flag_enabled!(tree, args.process, config);
|
||||||
let is_default_command = is_flag_enabled!(process_command, args.process, config);
|
let is_default_command = is_flag_enabled!(process_command, args.process, config);
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
let is_advanced_kill = !(is_flag_enabled!(disable_advanced_kill, args.process, config));
|
let is_advanced_kill = !(is_flag_enabled!(disable_advanced_kill, args.process, config));
|
||||||
let process_memory_as_value = is_flag_enabled!(process_memory_as_value, args.process, config);
|
let process_memory_as_value = is_flag_enabled!(process_memory_as_value, args.process, config);
|
||||||
|
|
||||||
@ -296,6 +297,7 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
|
|||||||
args.general,
|
args.general,
|
||||||
config
|
config
|
||||||
),
|
),
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
is_advanced_kill,
|
is_advanced_kill,
|
||||||
memory_legend_position,
|
memory_legend_position,
|
||||||
network_legend_position,
|
network_legend_position,
|
||||||
|
@ -303,7 +303,7 @@ pub struct ProcessArgs {
|
|||||||
)]
|
)]
|
||||||
pub current_usage: bool,
|
pub current_usage: bool,
|
||||||
|
|
||||||
// TODO: Disable this on Windows?
|
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
action = ArgAction::SetTrue,
|
action = ArgAction::SetTrue,
|
||||||
|
@ -36,7 +36,8 @@ pub(crate) struct FlagConfig {
|
|||||||
pub(crate) tree: Option<bool>,
|
pub(crate) tree: Option<bool>,
|
||||||
pub(crate) show_table_scroll_position: Option<bool>,
|
pub(crate) show_table_scroll_position: Option<bool>,
|
||||||
pub(crate) process_command: Option<bool>,
|
pub(crate) process_command: Option<bool>,
|
||||||
pub(crate) disable_advanced_kill: Option<bool>,
|
// #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
|
||||||
|
pub(crate) disable_advanced_kill: Option<bool>, // This does nothing on Windows, but we leave it enabled to make the config file consistent across platforms.
|
||||||
pub(crate) network_use_bytes: Option<bool>,
|
pub(crate) network_use_bytes: Option<bool>,
|
||||||
pub(crate) network_use_log: Option<bool>,
|
pub(crate) network_use_log: Option<bool>,
|
||||||
pub(crate) network_use_binary_prefix: Option<bool>,
|
pub(crate) network_use_binary_prefix: Option<bool>,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn sort_partial_fn<T: PartialOrd>(is_descending: bool) -> fn(T, T) -> Ordering {
|
pub(crate) const fn sort_partial_fn<T: PartialOrd>(is_descending: bool) -> fn(T, T) -> Ordering {
|
||||||
if is_descending {
|
if is_descending {
|
||||||
partial_ordering_desc
|
partial_ordering_desc
|
||||||
} else {
|
} else {
|
||||||
@ -11,7 +11,7 @@ pub const fn sort_partial_fn<T: PartialOrd>(is_descending: bool) -> fn(T, T) ->
|
|||||||
|
|
||||||
/// Returns an [`Ordering`] between two [`PartialOrd`]s.
|
/// Returns an [`Ordering`] between two [`PartialOrd`]s.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn partial_ordering<T: PartialOrd>(a: T, b: T) -> Ordering {
|
pub(crate) fn partial_ordering<T: PartialOrd>(a: T, b: T) -> Ordering {
|
||||||
a.partial_cmp(&b).unwrap_or(Ordering::Equal)
|
a.partial_cmp(&b).unwrap_or(Ordering::Equal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,12 +20,12 @@ pub fn partial_ordering<T: PartialOrd>(a: T, b: T) -> Ordering {
|
|||||||
/// This is simply a wrapper function around [`partial_ordering`] that reverses
|
/// This is simply a wrapper function around [`partial_ordering`] that reverses
|
||||||
/// the result.
|
/// the result.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn partial_ordering_desc<T: PartialOrd>(a: T, b: T) -> Ordering {
|
pub(crate) fn partial_ordering_desc<T: PartialOrd>(a: T, b: T) -> Ordering {
|
||||||
partial_ordering(a, b).reverse()
|
partial_ordering(a, b).reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for additional clamping functions on numeric types.
|
/// A trait for additional clamping functions on numeric types.
|
||||||
pub trait ClampExt {
|
pub(crate) trait ClampExt {
|
||||||
/// Restrict a value by a lower bound. If the current value is _lower_ than
|
/// Restrict a value by a lower bound. If the current value is _lower_ than
|
||||||
/// `lower_bound`, it will be set to `_lower_bound`.
|
/// `lower_bound`, it will be set to `_lower_bound`.
|
||||||
#[cfg_attr(not(test), expect(dead_code))]
|
#[cfg_attr(not(test), expect(dead_code))]
|
||||||
@ -63,12 +63,12 @@ macro_rules! clamp_num_impl {
|
|||||||
clamp_num_impl!(u8, u16, u32, u64, usize);
|
clamp_num_impl!(u8, u16, u32, u64, usize);
|
||||||
|
|
||||||
/// Checked log2.
|
/// Checked log2.
|
||||||
pub fn saturating_log2(value: f64) -> f64 {
|
pub(crate) fn saturating_log2(value: f64) -> f64 {
|
||||||
if value > 0.0 { value.log2() } else { 0.0 }
|
if value > 0.0 { value.log2() } else { 0.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checked log10.
|
/// Checked log10.
|
||||||
pub fn saturating_log10(value: f64) -> f64 {
|
pub(crate) fn saturating_log10(value: f64) -> f64 {
|
||||||
if value > 0.0 { value.log10() } else { 0.0 }
|
if value > 0.0 { value.log10() } else { 0.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +59,7 @@ pub fn kill_process_given_pid(pid: Pid) -> anyhow::Result<()> {
|
|||||||
/// Kills a process, given a PID, for UNIX.
|
/// Kills a process, given a PID, for UNIX.
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub fn kill_process_given_pid(pid: Pid, signal: usize) -> anyhow::Result<()> {
|
pub fn kill_process_given_pid(pid: Pid, signal: usize) -> anyhow::Result<()> {
|
||||||
// SAFETY: the signal should be valid, and we act properly on an error (exit
|
// SAFETY: the signal should be valid, and we act properly on an error (exit code not 0).
|
||||||
// code not 0).
|
|
||||||
let output = unsafe { libc::kill(pid, signal as i32) };
|
let output = unsafe { libc::kill(pid, signal as i32) };
|
||||||
|
|
||||||
if output != 0 {
|
if output != 0 {
|
Loading…
x
Reference in New Issue
Block a user