.
kmon-1.7.1/README.md 0000644 0000000 0000000 00000070307 10461020230 0012064 0 ustar 0000000 0000000
Linux Kernel Manager and Activity Monitor 🐧💻
**The kernel** is the part of the operating system that facilitates interactions between _hardware_ and _software_ components. On most systems, it is loaded on startup after the _bootloader_ and handles I/O requests as well as peripherals like keyboards, monitors, network adapters, and speakers. Typically, the kernel is responsible for **memory management**, **process management**, **device management**, **system calls**, and **security**.
Applications use the **system call** mechanism for requesting a service from the operating system and most of the time, this request is passed to the kernel using a library provided by the operating system to invoke the related kernel function. While the kernel performs these low-level tasks, it's resident on a separate part of memory named **protected kernel space** which is not accessible by applications and other parts of the system. In contrast, applications like browsers, text editors, window managers or audio/video players use a different separate area of the memory, **user space**. This separation prevents user data and kernel data from interfering with each other and causing instability and slowness, as well as preventing malfunctioning application programs from crashing the entire operating system.
There are different kernel designs due to the different ways of managing system calls and resources. For example, while **monolithic kernels** run all the operating system instructions in the same address space _for speed_, **microkernels** use different spaces for user and kernel services _for modularity_. Apart from those, there are **hybrid kernels**, **nanokernels**, and, **exokernels**. The hybrid kernel architecture is based on combining aspects of microkernel and monolithic kernels.
**The Linux kernel** is the open-source, monolithic and, Unix-like operating system kernel that used in the Linux distributions, various embedded systems such as routers and as well as in the all Android-based systems. **Linus Torvalds** conceived and created the Linux kernel in 1991 and it's still being developed by thousands of developers today. It's a prominent example of **free and open source software** and it's used in other free software projects, notably the **GNU operating system**.
Although the Linux-based operating systems dominate the most of computing, it still carries some of the design flaws which were quite a bit of debate in the early days of Linux. For example, it has the **largest footprint** and **the most complexity** over the other types of kernels. But it's a design feature that monolithic kernels inherent to have. These kind of design issues led developers to add new features and mechanisms to the Linux kernel which other kernels don't have.
Unlike the standard monolithic kernels, the Linux kernel is also **modular**, accepting **loadable kernel modules (LKM)** that typically used to add support for new _hardware_ (as device drivers) and/or _filesystems_, or for adding _system calls_. Since LKMs could be loaded and unloaded to the system _at runtime_, they have the advantage of extending the kernel without rebooting and re-compiling. Thus, the kernel functionalities provided by modules would not reside in memory without being used and the related module can be unloaded in order to free memory and other resources.
Loadable kernel modules are located in `/lib/modules` with the `.ko` (_kernel object_) extension in Linux. While the [lsmod](https://linux.die.net/man/8/lsmod) command could be used for listing the loaded kernel modules, [modprobe](https://linux.die.net/man/8/modprobe) or [insmod](https://linux.die.net/man/8/insmod)/[rmmod](https://linux.die.net/man/8/rmmod) is used for loading or unloading a kernel module. insmod/rmmod are used for modules independent of modprobe and without requiring an installation to `/lib/modules/$(uname -r)`.
Here's a simple example of a Linux kernel module that prints a message when it's loaded and unloaded. The build and installation steps of the [module](https://github.com/orhun/kmon/blob/master/example/lkm_example.c) using a [Makefile](https://github.com/orhun/kmon/blob/master/example/Makefile) are shown below.
```
make # build
sudo make install # install
sudo modprobe lkm_example # load
sudo modprobe -r lkm_example # unload
```
The [dmesg](https://linux.die.net/man/8/dmesg) command is used below to retrieve the message buffer of the kernel.
```
[16994.295552] [+] Example kernel module loaded.
[16996.325674] [-] Example kernel module unloaded.
```
**kmon** provides a [text-based user interface](https://en.wikipedia.org/wiki/Text-based_user_interface) for managing the Linux kernel modules and monitoring the kernel activities. By managing, it means loading, unloading, blacklisting and showing the information of a module. These updates in the kernel modules, logs about the hardware and other kernel messages can be tracked with the real-time activity monitor in kmon. Since the usage of different tools like [dmesg](https://en.wikipedia.org/wiki/Dmesg) and [kmod](https://www.linux.org/docs/man8/kmod.html) are required for these tasks in Linux, kmon aims to gather them in a single terminal window and facilitate the usage as much as possible while keeping the functionality.
kmon is written in [Rust](https://www.rust-lang.org/) and uses [Ratatui](https://ratatui.rs) & [termion](https://github.com/redox-os/termion) libraries for its text-based user interface.
### Table of Contents
- [Installation](#installation)
- [Cargo](#cargo)
- [Arch Linux](#arch-linux)
- [Nixpkgs](#nixpkgs)
- [Alpine Linux](#alpine-linux)
- [Docker](#docker)
- [Build](#build)
- [Run](#run)
- [Manual](#manual)
- [Note](#note)
- [Usage](#usage)
- [Options](#options)
- [Commands](#commands)
- [Sort](#sort)
- [Key Bindings](#key-bindings)
- [Features](#features)
- [Help](#help)
- [Navigating & Scrolling](#navigating--scrolling)
- [Scrolling Kernel Activities](#scrolling-kernel-activities)
- [Smooth Scrolling](#smooth-scrolling)
- [Options Menu](#options-menu)
- [Block Sizes](#block-sizes)
- [Block Positions](#block-positions)
- [Kernel Information](#kernel-information)
- [Module Information](#module-information)
- [Displaying the dependent modules](#displaying-the-dependent-modules)
- [Jumping to dependent modules](#jumping-to-dependent-modules)
- [Searching a module](#searching-a-module)
- [Loading a module](#loading-a-module)
- [Unloading a module](#unloading-a-module)
- [Blacklisting a module](#blacklisting-a-module)
- [Reloading a module](#reloading-a-module)
- [Clearing the ring buffer](#clearing-the-ring-buffer)
- [Copy & Paste](#copy--paste)
- [Sorting/reversing the kernel modules](#sortingreversing-the-kernel-modules)
- [Customizing the colors](#customizing-the-colors)
- [Supported colors](#supported-colors)
- [Using a custom color](#using-a-custom-color)
- [Changing the accent color](#changing-the-accent-color)
- [Unicode symbols](#unicode-symbols)
- [Setting the terminal tick rate](#setting-the-terminal-tick-rate)
- [Searching modules by regular expression](#searching-modules-by-regular-expression)
- [Roadmap](#roadmap)
- [Accessibility](#accessibility)
- [Dependencies](#dependencies)
- [Features](#features-1)
- [Testing](#testing)
- [Resources](#resources)
- [About the project](#about-the-project)
- [Articles](#articles)
- [In the media](#in-the-media)
- [Gallery](#gallery)
- [Social Media](#social-media)
- [Funding](#funding)
- [GitHub](#github)
- [Patreon](#patreon)
- [Open Collective](#open-collective)
- [License](#license)
- [Copyright](#copyright)
## Installation
[](https://repology.org/project/kmon/versions)
### Cargo
**kmon** can be installed from [crates.io](https://crates.io/crates/kmon/) using Cargo if [Rust](https://www.rust-lang.org/tools/install) is installed.
```
cargo install kmon
```
The minimum supported Rust version (MSRV) is `1.74.1`.
### Arch Linux
**kmon** can be installed from the Arch Linux [official repository](https://www.archlinux.org/packages/extra/x86_64/kmon/).
```
pacman -S kmon
```
There is also a development package on the [AUR](https://aur.archlinux.org/packages/kmon-git/). Use your favorite [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) to install. For example,
```
paru -S kmon-git
```
### Nixpkgs
**kmon** can be installed using [Nix package manager](https://nixos.org/nix/) from `nixpkgs-unstable` channel.
```
nix-channel --add https://nixos.org/channels/nixpkgs-unstable
nix-channel --update nixpkgs
nix-env -iA nixpkgs.kmon
```
On [NixOS](https://nixos.org/nixos/):
```
nix-channel --add https://nixos.org/channels/nixos-unstable
nix-channel --update nixos
nix-env -iA nixos.kmon
```
### Alpine Linux
**kmon** is available for [Alpine Edge](https://pkgs.alpinelinux.org/packages?name=kmon&branch=edge). It can be installed via [apk](https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper) after enabling the [community repository](https://wiki.alpinelinux.org/wiki/Repositories).
```
apk add kmon
```
### Docker
[](https://hub.docker.com/r/orhunp/kmon)
```
docker run -it --cap-add syslog orhunp/kmon:tagname
```
#### Build
```
docker build -t kmon .
```
#### Run
```
docker run -it --cap-add syslog kmon
```
### Manual
1. Download the latest binary from [releases](https://github.com/orhun/kmon/releases) section and pick between [glibc](https://en.wikipedia.org/wiki/Glibc) or [musl-libc](https://musl.libc.org/) binary.
2. To download the package compiled with [glibc](https://en.wikipedia.org/wiki/Glibc) run:
```
wget https://github.com/orhun/kmon/releases/download/v[VERSION]/kmon-[VERSION]-x86_64-unknown-linux-gnu.tar.gz
```
3. To download the package compiled with [musl-libc](https://musl.libc.org/) run:
```
wget https://github.com/orhun/kmon/releases/download/v[VERSION]/kmon-[VERSION]-x86_64-unknown-linux-musl.tar.gz
```
3. Extract the files.
```
tar -xvzf kmon-*.tar.gz
```
4. Enter in the new folder.
```
cd kmon-[VERSION]
```
5. Run the binary.
```
./kmon
```
6. Move binary to `/usr/local/bin/` for running it from the terminal using `kmon` command.
7. Man page and shell completions are generated at build time in `target` directory.
#### Note
[libxcb](https://xcb.freedesktop.org/) should be installed for using the copy/paste commands of X11.
e.g: Install `libxcb1-dev` package for Debian/Ubuntu[\*](https://github.com/orhun/kmon/issues/2) and `libxcb-devel` package for Fedora/openSUSE/Void Linux.
## Usage
```
kmon [OPTIONS] [COMMAND]
```
### Options
```
-a, --accent-color Set the accent color using hex or color name [default: white]
-c, --color Set the main color using hex or color name [default: darkgray]
-t, --tickrate Set the refresh rate of the terminal [default: 250]
-r, --reverse Reverse the kernel module list
-u, --unicode Show Unicode symbols for the block titles
-E, --regex Interpret the module search query as a regular expression
-h, --help Print help information
-V, --version Print version information
```
### Commands
```
sort Sort kernel modules
```
#### Sort
```
kmon sort [OPTIONS]
```
**Options:**
```
-s, --size Sort modules by their sizes
-n, --name Sort modules by their names
-d, --dependent Sort modules by their dependent modules
-h, --help Print help information
```
## Key Bindings
| | |
| ----------------------- | ------------------------------------- |
| `[?], F1` | Help |
| `right/left, h/l` | Switch between blocks |
| `up/down, k/j, alt-k/j` | Scroll up/down [selected block] |
| `pgup/pgdown` | Scroll up/down [kernel activities] |
| `>` | Scroll up/down [module information] |
| `alt-h/l` | Scroll right/left [kernel activities] |
| `ctrl-t/b, home/end` | Scroll to top/bottom [module list] |
| `alt-e/s` | Expand/shrink the selected block |
| `ctrl-x` | Change the block position |
| `ctrl-l/u, alt-c` | Clear the kernel ring buffer |
| `[d], alt-d` | Show the dependent modules |
| `[1]..[9]` | Jump to the dependent module |
| `[\], tab, backtab` | Show the next kernel information |
| `[/], s, enter` | Search a kernel module |
| `[+], i, insert` | Load a kernel module |
| `[-], u, backspace` | Unload the kernel module |
| `[x], b, delete` | Blacklist the kernel module |
| `ctrl-r, alt-r` | Reload the kernel module |
| `m, o` | Show the options menu |
| `y/n` | Execute/cancel the command |
| `c/v` | Copy/paste |
| `r, F5` | Refresh |
| `q, ctrl-c/d, ESC` | Quit |
## Features
### Help
Press '`?`' while running the terminal UI to see key bindings.

### Navigating & Scrolling
`Arrow keys` are used for navigating between blocks and scrolling.

#### Scrolling Kernel Activities
Some kernel messages might be long enough for not fitting into the kernel activities block since they are not wrapped. In this situation, kernel activities can be scrolled horizontally with `alt-h & alt-l` keys. Vertical scrolling mechanism is the same as other blocks.

#### Smooth Scrolling
`alt-j & alt-k` keys can be used to scroll kernel activity and module information blocks slowly.

### Options Menu
`m` and `o` keys can be used as a shortcut for kernel management operations. When pressed, an options menu will be provided for managing the currently selected kernel module.

### Block Sizes
`alt-e & alt-s` keys can be used for expanding/shrinking the selected block.

### Block Positions
`ctrl-x` key can be used for changing the positions of blocks.

### Kernel Information
Use one of the `\, tab, backtab` keys to switch between kernel release, version and platform information.

### Module Information
The status of a kernel module is shown on selection.

#### Displaying the dependent modules
Use one of the `d, alt-d` keys to show all the dependent modules of the selected module.

#### Jumping to dependent modules
For jumping to a dependent kernel module from its parent module, `number keys` (1-9) can be used for specifying the index of the module on the _Used By_ column.

### Searching a module
Switch to the search area with arrow keys or using one of the `/, s, enter` and provide a search query for the module name.

### Loading a module
For adding a module to the Linux kernel, switch to load mode with one of the `+, i, insert` keys and provide the name of the module to load. Then confirm/cancel the execution of the load command with `y/n`.

The command that used for loading a module:
```
modprobe || insmod .ko
```
### Unloading a module
Use one of the `-, u, backspace` keys to remove the selected module from the Linux kernel.

The command that used for removing a module:
```
modprobe -r || rmmod
```
### Blacklisting a module
[Blacklisting](https://wiki.archlinux.org/index.php/Kernel_module#Blacklisting) is a mechanism to prevent the kernel module from loading. To blacklist the selected module, use one of the `x, b, delete` keys and confirm the execution.

The command that used for blacklisting a module:
```
if ! grep -q /etc/modprobe.d/blacklist.conf; then
echo 'blacklist ' >> /etc/modprobe.d/blacklist.conf
echo 'install /bin/false' >> /etc/modprobe.d/blacklist.conf
fi
```
### Reloading a module
Use `ctrl-r` or `alt-r` key for reloading the selected module.

The command that used for reloading a module:
```
modprobe -r || rmmod && modprobe || insmod .ko
```
### Clearing the ring buffer
The kernel ring buffer can be cleared with using one of the `ctrl-l/u, alt-c` keys.

```
dmesg --clear
```
### Copy & Paste
`c/v` keys are set for copy/paste operations.

Use `ctrl-c/ctrl-v` for copying and pasting while in input mode.
### Sorting/reversing the kernel modules
`sort` subcommand can be used for sorting the kernel modules by their names, sizes or dependent modules.
```
kmon sort --name
kmon sort --size
kmon sort --dependent
```

Also the `-r, --reverse` flag is used for reversing the kernel module list.
```
kmon --reverse
```

### Customizing the colors
kmon uses the colors of the terminal as default but the highlighting color could be specified with `-c, --color` option. Alternatively, default text color can be set via `-a, --accent-color` option.
#### Supported colors
Supported terminal colors are `black, red, green, yellow, blue, magenta, cyan, gray, darkgray, lightred, lightgreen, lightyellow, lightblue, lightmagenta, lightcyan, white`.
```
kmon --color red
```

#### Using a custom color
Provide a hexadecimal value for the color to use.
```
kmon --color 19683a
```

#### Changing the accent color
Default text color might cause readability issues on some themes that have transparency. `-a, --accent-color` option can be used similarly to the `-c, --color` option for overcoming this issue.
```
kmon --color 6f849c --accent-color e35760
```

### Unicode symbols
Use `-u, --unicode` flag for showing Unicode symbols for the block titles.
```
kmon --unicode
```

### Setting the terminal tick rate
`-t, --tickrate` option can be used for setting the refresh interval of the terminal UI in milliseconds.

### Searching modules by regular expression
`-E, --regex` option can be used for searching modules by regular expression.
## Roadmap
kmon aims to be a standard tool for Linux kernel management while supporting most of the Linux distributions.
### Accessibility
For achieving this goal, kmon should be accessible from different package managers such as [Snap](https://snapcraft.io/)[\*](https://forum.snapcraft.io/t/unable-to-load-modules-to-kernel-and-get-module-information/16151) and [RPM](https://rpm.org/).
### Dependencies
It is required to have the essential tools like [dmesg](https://en.wikipedia.org/wiki/Dmesg) and [kmod](https://www.linux.org/docs/man8/kmod.html) on the system for kmon to work as expected. Thus the next step would be using just the system resources for these functions.
### Features
Management actions about the Linux kernel should be applicable in kmon for minimizing the dependence on to command line and other tools.
### Testing
kmon should be tested and reported on different architectures for further development and support.
## Resources
### About the project
- [Code of conduct](https://github.com/orhun/kmon/blob/master/CODE_OF_CONDUCT.md)
- [Contributing](https://github.com/orhun/kmon/blob/master/CONTRIBUTING.md)
- [Creating a release](https://github.com/orhun/kmon/blob/master/RELEASE.md)
### Articles
- [Exploring the Linux Kernel by Bob Cromwell](https://cromwell-intl.com/open-source/linux-kernel-details.html)
- [Anatomy of the Linux loadable kernel module by Terenceli](https://terenceli.github.io/%E6%8A%80%E6%9C%AF/2018/06/02/linux-loadable-module)
- [Managing kernel modules with kmod by Lucas De Marchi](https://elinux.org/images/8/89/Managing_Kernel_Modules_With_kmod.pdf)
### In the media
- [Manage And Monitor Linux Kernel Modules With Kmon](https://ostechnix.com/manage-and-monitor-linux-kernel-modules-with-kmon/) (
OSTechNix)
- [Kmon The Linux Kernel Management And Monitoring Software](https://www.youtube.com/watch?v=lukxf6CnR2o) (Brodie Robertson on YouTube)
### Gallery
| Fedora 31 | Debian 10 | Manjaro 19 |
| :---------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------: |
|  |  |  |
| Ubuntu 18.04 | openSUSE | Void Linux |
| :---------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: |
|  |  |  |
### Social Media
- Follow [@kmonitor\_](https://twitter.com/kmonitor_) on Twitter
- Follow the [author](https://orhun.dev/):
- [@orhun](https://github.com/orhun) on GitHub
- [@orhundev](https://twitter.com/orhundev) on Twitter
## Funding
### GitHub
Support the development of my projects by supporting me on [GitHub Sponsors](https://github.com/sponsors/orhun).
### Patreon
[](https://www.patreon.com/join/orhunp)
### Open Collective
[](https://opencollective.com/kmon) [](https://opencollective.com/kmon)
Support the open source development efforts by becoming a [backer](https://opencollective.com/kmon/contribute/backer-15060/checkout) or [sponsor](https://opencollective.com/kmon/contribute/sponsor-15061/checkout).
[](https://opencollective.com/kmon/donate)
## License
GNU General Public License ([3.0](https://www.gnu.org/licenses/gpl.txt))
## Copyright
Copyright © 2020-2024, [Orhun Parmaksız](mailto:orhunparmaksiz@gmail.com)
kmon-1.7.1/src/app.rs 0000644 0000000 0000000 00000042350 10461020230 0012517 0 ustar 0000000 0000000 use crate::event::Event;
use crate::kernel::cmd::ModuleCommand;
use crate::kernel::lkm::KernelModules;
use crate::kernel::log::KernelLogs;
use crate::kernel::Kernel;
use crate::style::{Style, StyledText, Symbol};
use crate::util;
use crate::widgets::StatefulList;
use copypasta_ext::display::DisplayServer as ClipboardDisplayServer;
use copypasta_ext::ClipboardProviderExt;
use enum_iterator::Sequence;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::Style as TuiStyle;
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{
Block as TuiBlock, Borders, Clear, List, ListItem, Paragraph, Row, Table, Wrap,
};
use ratatui::Frame;
use regex_lite::RegexBuilder;
use std::fmt::{Debug, Display, Formatter};
use std::slice::Iter;
use std::sync::mpsc::Sender;
use termion::event::Key;
use unicode_width::UnicodeWidthStr;
/// Table header of the module table
pub const TABLE_HEADER: &[&str] = &[" Module", "Size", "Used by"];
/// Available options in the module management menu
const OPTIONS: &[(&str, &str)] = &[
("unload", "Unload the module"),
("reload", "Reload the module"),
("blacklist", "Blacklist the module"),
("dependent", "Show the dependent modules"),
("copy", "Copy the module name"),
("load", "Load a kernel module"),
("clear", "Clear the ring buffer"),
];
/// Supported directions of scrolling
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScrollDirection {
Up,
Down,
Left,
Right,
Top,
Bottom,
}
impl ScrollDirection {
/// Return iterator of the available scroll directions.
#[allow(dead_code)]
pub fn iter() -> Iter<'static, ScrollDirection> {
[
ScrollDirection::Up,
ScrollDirection::Down,
ScrollDirection::Left,
ScrollDirection::Right,
ScrollDirection::Top,
ScrollDirection::Bottom,
]
.iter()
}
}
/// Main blocks of the terminal
#[derive(Clone, Copy, Debug, PartialEq, Eq, Sequence)]
pub enum Block {
UserInput,
ModuleTable,
ModuleInfo,
Activities,
}
/// Sizes of the terminal blocks
pub struct BlockSize {
pub input: u16,
pub info: u16,
pub activities: u16,
}
/// Default initialization values for BlockSize
impl Default for BlockSize {
fn default() -> Self {
Self {
input: 60,
info: 40,
activities: 25,
}
}
}
/// User input mode
#[derive(Clone, Copy, Debug, PartialEq, Eq, Sequence)]
pub enum InputMode {
None,
Search,
Load,
}
impl InputMode {
/// Check if input mode is set.
pub fn is_none(self) -> bool {
self == Self::None
}
}
/// Implementation of Display for using InputMode members as string
impl Display for InputMode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut input_mode = *self;
if input_mode.is_none() {
input_mode = match InputMode::first().and_then(|v| v.next()) {
Some(v) => v,
None => input_mode,
}
}
write!(f, "{input_mode:?}")
}
}
/// Application settings and related methods
pub struct App {
pub selected_block: Block,
pub default_block: Block,
pub block_size: BlockSize,
pub block_index: u8,
pub input_mode: InputMode,
pub input_query: String,
pub options: StatefulList<(String, String)>,
pub show_options: bool,
style: Style,
clipboard: Option>,
}
impl App {
/// Create a new app instance.
pub fn new(block: Block, style: Style) -> Self {
Self {
selected_block: block,
default_block: block,
block_size: BlockSize::default(),
block_index: 0,
input_mode: InputMode::None,
input_query: String::new(),
options: StatefulList::with_items(
OPTIONS
.iter()
.map(|(option, text)| {
(String::from(*option), String::from(*text))
})
.collect(),
),
show_options: false,
style,
clipboard: match ClipboardDisplayServer::select().try_context() {
None => {
eprintln!("failed to initialize clipboard, no suitable clipboard provider found");
None
}
clipboard => clipboard,
},
}
}
/// Reset app properties to default.
pub fn refresh(&mut self) {
self.selected_block = self.default_block;
self.block_size = BlockSize::default();
self.block_index = 0;
self.input_mode = InputMode::None;
self.input_query = String::new();
self.options.state.select(Some(0));
self.show_options = false;
}
/// Get style depending on the selected state of the block.
pub fn block_style(&self, block: Block) -> TuiStyle {
if self.show_options {
self.style.colored
} else if block == self.selected_block {
self.style.default
} else {
self.style.colored
}
}
/// Get the size of the selected block.
pub fn block_size(&mut self) -> &mut u16 {
match self.selected_block {
Block::ModuleInfo => &mut self.block_size.info,
Block::Activities => &mut self.block_size.activities,
_ => &mut self.block_size.input,
}
}
/// Get clipboard contents as String.
pub fn get_clipboard_contents(&mut self) -> String {
if let Some(clipboard) = self.clipboard.as_mut() {
if let Ok(contents) = clipboard.get_contents() {
return contents;
}
}
String::new()
}
/// Set clipboard contents.
pub fn set_clipboard_contents(&mut self, contents: &str) {
if let Some(clipboard) = self.clipboard.as_mut() {
if let Err(e) = clipboard.set_contents(contents.to_string()) {
eprintln!("{e}");
}
}
}
/// Show help message on the information block.
pub fn show_help_message(&mut self, kernel_modules: &mut KernelModules<'_>) {
let key_bindings: Vec<(&str, &str)> = util::KEY_BINDINGS.to_vec();
let mut help_text = Vec::new();
let mut help_text_raw = Vec::new();
for (key, desc) in &key_bindings {
help_text.push(Line::from(Span::styled(
format!("{}:", &key),
self.style.colored,
)));
help_text_raw.push(format!("{key}:"));
help_text.push(Line::from(Span::styled(
format!("{}{}", self.style.unicode.get(Symbol::Blank), &desc),
self.style.default,
)));
help_text_raw.push(format!(" {}", &desc));
}
kernel_modules.info_scroll_offset = 0;
kernel_modules.command = ModuleCommand::None;
kernel_modules.current_name =
format!("!Help{}", self.style.unicode.get(Symbol::Helmet));
kernel_modules
.current_info
.set(Text::from(help_text), help_text_raw.join("\n"));
}
/// Show dependent modules on the information block.
#[allow(clippy::nonminimal_bool)]
pub fn show_dependent_modules(
&mut self,
kernel_modules: &mut KernelModules<'_>,
) {
let dependent_modules_list = kernel_modules.default_list
[kernel_modules.index][2]
.split(' ')
.last()
.unwrap_or("-")
.split(',')
.collect::>();
if !(dependent_modules_list[0] == "-"
|| kernel_modules.current_name.contains("Dependent modules"))
|| cfg!(test)
{
kernel_modules.info_scroll_offset = 0;
kernel_modules.command = ModuleCommand::None;
kernel_modules.current_name = format!(
"!Dependent modules of {}{}",
kernel_modules.current_name,
self.style.unicode.get(Symbol::HistoricSite)
);
let mut dependent_modules = Vec::new();
for module in &dependent_modules_list {
dependent_modules.push(Line::from(vec![
Span::styled("-", self.style.colored),
Span::styled(format!(" {module}"), self.style.default),
]));
}
kernel_modules.current_info.set(
Text::from(dependent_modules),
kernel_modules.current_name.clone(),
);
}
}
/// Draw a block according to the index.
pub fn draw_dynamic_block(
&mut self,
frame: &mut Frame,
area: Rect,
kernel: &mut Kernel,
) {
match self.block_index {
0 => self.draw_kernel_modules(frame, area, &mut kernel.modules),
1 => self.draw_module_info(frame, area, &mut kernel.modules),
_ => self.draw_kernel_activities(frame, area, &mut kernel.logs),
}
if self.block_index < 2 {
self.block_index += 1;
} else {
self.block_index = 0;
}
}
/// Draw a paragraph widget for using as user input.
pub fn draw_user_input(
&self,
frame: &mut Frame,
area: Rect,
tx: &Sender>,
) {
frame.render_widget(
Paragraph::new(Span::raw(self.input_query.to_string()))
.block(
TuiBlock::default()
.border_style(match self.selected_block {
Block::UserInput => {
if self.input_mode.is_none() {
tx.send(Event::Input(Key::Char('\n'))).unwrap();
}
self.style.default
}
_ => self.style.colored,
})
.borders(Borders::ALL)
.title(Span::styled(
format!(
"{}{}",
self.input_mode,
match self.input_mode {
InputMode::Load =>
self.style.unicode.get(Symbol::Anchor),
_ => self.style.unicode.get(Symbol::Magnifier),
}
),
self.style.bold,
)),
)
.alignment(Alignment::Left),
area,
);
}
/// Draw a paragraph widget for showing the kernel information.
pub fn draw_kernel_info(&self, frame: &mut Frame, area: Rect, info: &[String]) {
frame.render_widget(
Paragraph::new(Span::raw(&info[1]))
.block(
TuiBlock::default()
.border_style(self.style.colored)
.borders(Borders::ALL)
.title(Span::styled(
format!(
"{}{}",
info[0],
self.style.unicode.get(Symbol::Gear)
),
self.style.bold,
))
.title_alignment(Alignment::Center),
)
.alignment(Alignment::Center)
.wrap(Wrap { trim: true }),
area,
);
}
/// Configure and draw kernel modules table.
pub fn draw_kernel_modules(
&mut self,
frame: &mut Frame,
area: Rect,
kernel_modules: &mut KernelModules<'_>,
) {
// Filter the module list depending on the input query.
let mut kernel_module_list = kernel_modules.default_list.clone();
match self.input_mode {
InputMode::None | InputMode::Search if !self.input_query.is_empty() => {
if kernel_modules.args.regex() {
if let Ok(regex) = RegexBuilder::new(&self.input_query)
.case_insensitive(true)
.build()
{
kernel_module_list
.retain(|module| regex.is_match(module[0].trim_start()))
}
} else {
let input_query = &self.input_query.to_lowercase();
kernel_module_list.retain(|module| {
module[0].to_lowercase().contains(input_query)
});
}
}
_ => {}
}
// Append '...' if dependent modules exceed the block width.
let dependent_width = (area.width / 2).saturating_sub(7) as usize;
for module in &mut kernel_module_list {
if module[2].len() > dependent_width {
module[2].truncate(dependent_width);
module[2].push_str("...");
}
}
kernel_modules.list = kernel_module_list;
// Set the scroll offset for modules.
let modules_scroll_offset = area
.height
.checked_sub(5)
.and_then(|height| kernel_modules.index.checked_sub(height as usize))
.unwrap_or(0);
// Set selected state of the modules and render the table widget.
frame.render_widget(
Table::new(
kernel_modules
.list
.iter()
.skip(modules_scroll_offset)
.enumerate()
.map(|(i, item)| {
let item = item.iter().map(|v| v.to_string());
if Some(i)
== kernel_modules
.index
.checked_sub(modules_scroll_offset)
{
Row::new(item).style(self.style.default)
} else {
Row::new(item).style(self.style.colored)
}
}),
&[
Constraint::Percentage(30),
Constraint::Percentage(20),
Constraint::Percentage(50),
],
)
.header(
Row::new(TABLE_HEADER.iter().map(|v| v.to_string()))
.style(self.style.bold),
)
.block(
TuiBlock::default()
.border_style(self.block_style(Block::ModuleTable))
.borders(Borders::ALL)
.title(Span::styled(
format!(
"Loaded Kernel Modules {}{}/{}{} {}{}%{}",
self.style.unicode.get(Symbol::LeftBracket),
match kernel_modules.list.len() {
0 => kernel_modules.index,
_ => kernel_modules.index + 1,
},
kernel_modules.list.len(),
self.style.unicode.get(Symbol::RightBracket),
self.style.unicode.get(Symbol::LeftBracket),
if !kernel_modules.list.is_empty() {
((kernel_modules.index + 1) as f64
/ kernel_modules.list.len() as f64
* 100.0) as u64
} else {
0
},
self.style.unicode.get(Symbol::RightBracket),
),
self.style.bold,
)),
),
area,
);
if self.show_options {
self.draw_options_menu(frame, area, kernel_modules);
}
}
/// Draws the options menu as a popup.
pub fn draw_options_menu(
&mut self,
frame: &mut Frame,
area: Rect,
kernel_modules: &mut KernelModules<'_>,
) {
let block_title = format!(
"Options ({})",
kernel_modules.list[kernel_modules.index][0]
.split_whitespace()
.next()
.unwrap_or("?")
.trim()
);
let items = self
.options
.items
.iter()
.map(|(_, text)| ListItem::new(Span::raw(format!(" {text}"))))
.collect::>>();
let (mut percent_y, mut percent_x) = (40, 60);
let text_height = items.iter().map(|v| v.height() as f32).sum::() + 3.;
if area.height.checked_sub(5).unwrap_or(area.height) as f32 > text_height {
percent_y = ((text_height / area.height as f32) * 100.) as u16;
}
if let Some(text_width) = self
.options
.items
.iter()
.map(|(_, text)| text.width())
.chain(vec![block_title.width()])
.max()
.map(|v| v as f32 + 7.)
{
if area.width.checked_sub(2).unwrap_or(area.width) as f32 > text_width {
percent_x = ((text_width / area.width as f32) * 100.) as u16;
}
}
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
]
.as_ref(),
)
.split(area);
let popup_rect = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
]
.as_ref(),
)
.split(popup_layout[1])[1];
frame.render_widget(Clear, popup_rect);
frame.render_stateful_widget(
List::new(items)
.block(
TuiBlock::default()
.title(Span::styled(block_title, self.style.bold))
.title_alignment(Alignment::Center)
.style(self.style.default)
.borders(Borders::ALL),
)
.style(self.style.colored)
.highlight_style(self.style.default),
popup_rect,
&mut self.options.state,
);
}
/// Draw a paragraph widget for showing module information.
pub fn draw_module_info(
&self,
frame: &mut Frame,
area: Rect,
kernel_modules: &mut KernelModules<'_>,
) {
frame.render_widget(
Paragraph::new(kernel_modules.current_info.get())
.block(
TuiBlock::default()
.border_style(self.block_style(Block::ModuleInfo))
.borders(Borders::ALL)
.title(Span::styled(
format!(
"{}{}",
kernel_modules.get_current_command().title,
self.style.unicode.get(
kernel_modules.get_current_command().symbol
)
),
self.style.bold,
)),
)
.alignment(
if kernel_modules.command.is_none()
&& !kernel_modules
.current_info
.raw_text
.contains("Execution Error\n")
{
Alignment::Left
} else {
Alignment::Center
},
)
.wrap(Wrap { trim: true })
.scroll((kernel_modules.info_scroll_offset as u16, 0)),
area,
);
}
/// Draw a paragraph widget for showing kernel activities.
pub fn draw_kernel_activities(
&self,
frame: &mut Frame,
area: Rect,
kernel_logs: &mut KernelLogs,
) {
frame.render_widget(
Paragraph::new(StyledText::default().stylize_data(
kernel_logs.select(area.height, 2),
"] ",
self.style.clone(),
))
.block(
TuiBlock::default()
.border_style(self.block_style(Block::Activities))
.borders(Borders::ALL)
.title(Span::styled(
format!(
"Kernel Activities{}",
self.style.unicode.get(Symbol::HighVoltage)
),
self.style.bold,
)),
)
.alignment(Alignment::Left),
area,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::Events;
use crate::kernel::info;
use crate::kernel::lkm::ListArgs;
use clap::ArgMatches;
use ratatui::backend::TestBackend;
use ratatui::Terminal;
#[test]
fn test_app() {
let args = ArgMatches::default();
let mut kernel_modules =
KernelModules::new(ListArgs::new(&args), Style::new(&args));
let mut app = App::new(Block::ModuleTable, kernel_modules.style.clone());
app.set_clipboard_contents("test");
assert_ne!("x", app.get_clipboard_contents());
assert_eq!(app.style.default, app.block_style(Block::ModuleTable));
assert_eq!(app.style.colored, app.block_style(Block::Activities));
let mut kernel_logs = KernelLogs::default();
let backend = TestBackend::new(20, 10);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let size = f.size();
app.selected_block = Block::UserInput;
app.draw_user_input(f, size, &Events::new(100, &kernel_logs).tx);
app.draw_kernel_info(f, size, &info::KernelInfo::new().current_info);
app.input_query = String::from("a");
app.draw_kernel_modules(f, size, &mut kernel_modules);
app.draw_module_info(f, size, &mut kernel_modules);
app.draw_kernel_activities(f, size, &mut kernel_logs);
})
.unwrap();
}
#[test]
fn test_input_mode() {
let mut input_mode = InputMode::Load;
assert!(!input_mode.is_none());
assert!(input_mode.to_string().contains("Load"));
input_mode = InputMode::None;
assert!(input_mode.to_string().contains("Search"));
}
}
kmon-1.7.1/src/args.rs 0000644 0000000 0000000 00000005225 10461020230 0012673 0 ustar 0000000 0000000 use clap::{Arg, ArgAction, Command as App};
/// ASCII format of the project logo
const ASCII_LOGO: &str = "
`` ```````````` ```` ``````````` ```````````
:NNs `hNNNNNNNNNNNNh` sNNNy yNNNNNNNNNN+ dNNNNNNNNNN:
/MMMydMMyyyyyyydMMMMdhMMMMy yMMMyyyhMMMo dMMMyyydMMM/
/MMMMMMM` oMMMMMMMMMMy yMMM` -MMMo dMMN /MMM/
/MMMs:::hhhs oMMM+:::MMMNhhhNMMMdhhdMMMmhhhNMMN /MMM/
:mmm/ dmmh +mmm- `mmmmmmmmmmmmmmmmmmmmmmmmmd /mmm:
``` ``` ``` `````````````````````````` ```";
/// Parse command line arguments using clap.
pub fn get_args() -> App {
App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(concat!(
env!("CARGO_PKG_NAME"),
" ",
env!("CARGO_PKG_VERSION"),
"\n",
env!("CARGO_PKG_AUTHORS"),
"\n",
env!("CARGO_PKG_DESCRIPTION"),
"\n\n",
"Press '?' while running the terminal UI to see key bindings."
))
.before_help(ASCII_LOGO)
.arg(
Arg::new("accent-color")
.short('a')
.long("accent-color")
.value_name("COLOR")
.default_value("white")
.help("Set the accent color using hex or color name")
.num_args(1),
)
.arg(
Arg::new("color")
.short('c')
.long("color")
.value_name("COLOR")
.default_value("darkgray")
.help("Set the main color using hex or color name")
.num_args(1),
)
.arg(
Arg::new("rate")
.short('t')
.long("tickrate")
.value_name("MS")
.default_value("250")
.help("Set the refresh rate of the terminal")
.num_args(1),
)
.arg(
Arg::new("reverse")
.short('r')
.long("reverse")
.help("Reverse the kernel module list")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("unicode")
.short('u')
.long("unicode")
.help("Show Unicode symbols for the block titles")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("regex")
.short('E')
.long("regex")
.help("Interpret the module search query as a regular expression")
.action(ArgAction::SetTrue),
)
.subcommand(
App::new("sort")
.about("Sort kernel modules")
.arg(
Arg::new("size")
.short('s')
.long("size")
.help("Sort modules by their sizes")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("name")
.short('n')
.long("name")
.help("Sort modules by their names")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("dependent")
.short('d')
.long("dependent")
.help("Sort modules by their dependent modules")
.action(ArgAction::SetTrue),
),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_args() {
get_args().debug_assert();
}
}
kmon-1.7.1/src/event.rs 0000644 0000000 0000000 00000004550 10461020230 0013060 0 ustar 0000000 0000000 use crate::kernel::log::KernelLogs;
use std::io;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use termion::event::Key;
use termion::input::TermRead;
/// Terminal event methods
pub enum Event {
Input(I),
Kernel(String),
Tick,
}
/// Terminal events
#[allow(dead_code)]
pub struct Events {
pub tx: mpsc::Sender>,
pub rx: mpsc::Receiver>,
input_handler: thread::JoinHandle<()>,
kernel_handler: thread::JoinHandle<()>,
tick_handler: thread::JoinHandle<()>,
}
impl Events {
/// Create a new events instance.
pub fn new(refresh_rate: u64, kernel_logs: &KernelLogs) -> Self {
// Convert refresh rate to Duration from milliseconds.
let refresh_rate = Duration::from_millis(refresh_rate);
// Create a new asynchronous channel.
let (tx, rx) = mpsc::channel();
// Handle inputs using stdin stream and sender of the channel.
let input_handler = {
let tx = tx.clone();
thread::spawn(move || {
let stdin = io::stdin();
for key in stdin.keys().flatten() {
tx.send(Event::Input(key)).unwrap();
}
})
};
// Handle kernel logs using 'dmesg' output.
let kernel_handler = {
let tx = tx.clone();
let mut kernel_logs = kernel_logs.clone();
thread::spawn(move || loop {
if kernel_logs.update() {
tx.send(Event::Kernel(kernel_logs.output.to_string()))
.unwrap_or_default();
}
thread::sleep(refresh_rate * 10);
})
};
// Create a loop for handling events.
let tick_handler = {
let tx = tx.clone();
thread::spawn(move || loop {
tx.send(Event::Tick).unwrap_or_default();
thread::sleep(refresh_rate);
})
};
// Return events.
Self {
tx,
rx,
input_handler,
kernel_handler,
tick_handler,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn test_events() -> Result<(), Box> {
let kernel_logs = KernelLogs::default();
let events = Events::new(100, &kernel_logs);
let mut i = 0;
loop {
let tx = events.tx.clone();
thread::spawn(move || {
let _ = tx.send(Event::Input(Key::Char(
std::char::from_digit(i, 10).unwrap_or('x'),
)));
});
i += 1;
match events.rx.recv()? {
Event::Input(v) => {
if v == Key::Char('9') {
break;
}
}
Event::Tick => thread::sleep(Duration::from_millis(100)),
Event::Kernel(log) => assert!(!log.is_empty()),
}
}
Ok(())
}
}
kmon-1.7.1/src/kernel/cmd.rs 0000644 0000000 0000000 00000013400 10461020230 0013754 0 ustar 0000000 0000000 use crate::style::Symbol;
/// Kernel module related command
#[derive(Debug)]
pub struct Command {
pub cmd: String,
pub desc: &'static str,
pub title: String,
pub symbol: Symbol,
}
impl Command {
/// Create a new command instance.
fn new(
cmd: String,
desc: &'static str,
mut title: String,
symbol: Symbol,
) -> Self {
// Parse the command title if '!' is given.
if title.contains('!') {
title = (*title
.split('!')
.collect::>()
.last()
.unwrap_or(&""))
.to_string();
}
Self {
cmd,
desc,
title,
symbol,
}
}
}
/// Kernel module management commands
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ModuleCommand {
None,
Load,
Unload,
Reload,
Blacklist,
Clear,
}
impl TryFrom for ModuleCommand {
type Error = ();
fn try_from(s: String) -> Result {
match s.as_ref() {
"load" => Ok(Self::Load),
"unload" => Ok(Self::Unload),
"reload" => Ok(Self::Reload),
"blacklist" => Ok(Self::Blacklist),
"clear" => Ok(Self::Clear),
_ => Err(()),
}
}
}
impl ModuleCommand {
/// Get Command struct from a enum element.
pub fn get(self, module_name: &str) -> Command {
match self {
Self::None => Command::new(String::from(""), "", format!("Module: {module_name}"), Symbol::None),
Self::Load => Command::new(
if Self::is_module_filename(module_name) {
format!("insmod {}", &module_name)
} else {
format!("modprobe {0} || insmod {0}.ko", &module_name)
},
"Add and remove modules from the Linux Kernel\n
This command inserts a module to the kernel.",
format!("Load: {module_name}"), Symbol::Anchor),
Self::Unload => Command::new(
format!("modprobe -r {0} || rmmod {0}", &module_name),
"modprobe/rmmod: Add and remove modules from the Linux Kernel
modprobe -r, --remove or rmmod\n
This option causes modprobe to remove rather than insert a module. \
If the modules it depends on are also unused, modprobe will try to \
remove them too. \
For modules loaded with insmod rmmod will be used instead. \
There is usually no reason to remove modules, but some buggy \
modules require it. Your distribution kernel may not have been \
built to support removal of modules at all.",
format!("Remove: {module_name}"), Symbol::CircleX),
Self::Reload => Command::new(
format!("{} && {}",
ModuleCommand::Unload.get(module_name).cmd,
ModuleCommand::Load.get(module_name).cmd),
"modprobe/insmod/rmmod: Add and remove modules from the Linux Kernel\n
This command reloads a module, removes and inserts to the kernel.",
format!("Reload: {module_name}"), Symbol::FuelPump),
Self::Blacklist => Command::new(
format!("if ! grep -q {module} /etc/modprobe.d/blacklist.conf; then
echo 'blacklist {module}' >> /etc/modprobe.d/blacklist.conf
echo 'install {module} /bin/false' >> /etc/modprobe.d/blacklist.conf
fi", module = &module_name),
"This command blacklists a module and any other module that depends on it.\n
Blacklisting is a mechanism to prevent the kernel module from loading. \
This could be useful if, for example, the associated hardware is not needed, \
or if loading that module causes problems.
The blacklist command will blacklist a module so that it will not be loaded \
automatically, but the module may be loaded if another non-blacklisted module \
depends on it or if it is loaded manually. However, there is a workaround for \
this behaviour; the install command instructs modprobe to run a custom command \
instead of inserting the module in the kernel as normal, so the module will \
always fail to load.",
format!("Blacklist: {module_name}"), Symbol::SquareX),
Self::Clear => Command::new(
String::from("dmesg --clear"),
"dmesg: Print or control the kernel ring buffer
option: -C, --clear\n
Clear the ring buffer.",
String::from("Clear"), Symbol::Cloud),
}
}
/// Check if module command is set.
pub fn is_none(self) -> bool {
self == Self::None
}
/// Check if module name is a filename with suffix 'ko'
pub fn is_module_filename(module_name: &str) -> bool {
match module_name.split('.').collect::>().last() {
Some(v) => *v == "ko",
None => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_command() {
let module_command = ModuleCommand::None;
assert!(module_command == ModuleCommand::None);
assert_ne!("", ModuleCommand::None.get("test").title);
assert_ne!("", ModuleCommand::Load.get("module").desc);
assert_ne!("", ModuleCommand::Unload.get("!command").cmd);
assert_ne!("", ModuleCommand::Blacklist.get("~").cmd);
assert_eq!(
"modprobe test-module || insmod test-module.ko",
ModuleCommand::Load.get("test-module").cmd
);
assert_eq!(
"insmod test-module.ko",
ModuleCommand::Load.get("test-module.ko").cmd
);
assert_eq!(
"modprobe -r test-module || rmmod test-module",
ModuleCommand::Unload.get("test-module").cmd
);
assert_eq!(
"modprobe -r test-module.ko || rmmod test-module.ko",
ModuleCommand::Unload.get("test-module.ko").cmd
);
assert_eq!(
format!(
"{} && {}",
ModuleCommand::Unload.get("test-module").cmd,
ModuleCommand::Load.get("test-module").cmd
),
ModuleCommand::Reload.get("test-module").cmd,
);
assert_eq!(
format!(
"{} && {}",
ModuleCommand::Unload.get("test-module.ko").cmd,
ModuleCommand::Load.get("test-module.ko").cmd
),
ModuleCommand::Reload.get("test-module.ko").cmd,
);
}
}
kmon-1.7.1/src/kernel/info.rs 0000644 0000000 0000000 00000003274 10461020230 0014154 0 ustar 0000000 0000000 use crate::util;
use std::vec::IntoIter;
/// Kernel and system information
pub struct KernelInfo {
pub current_info: Vec,
uname_output: IntoIter>,
}
impl Default for KernelInfo {
fn default() -> Self {
Self::new()
}
}
impl KernelInfo {
/// Create a new kernel info instance.
pub fn new() -> Self {
let mut kernel_info = Self {
current_info: Vec::new(),
uname_output: Vec::new().into_iter(),
};
kernel_info.refresh();
kernel_info
}
/// Refresh the kernel information fields.
pub fn refresh(&mut self) {
self.uname_output = KernelInfo::get_infos();
self.next();
}
/// Select the next 'uname' output as kernel information.
pub fn next(&mut self) {
match self.uname_output.next() {
Some(v) => self.current_info = v,
None => self.refresh(),
}
}
/// Execute 'uname' command and return its output along with its description.
fn get_infos() -> IntoIter> {
vec![
vec![
String::from("Kernel Release"),
util::exec_cmd("uname", &["-srn"])
.unwrap_or_else(|_| String::from("?")),
],
vec![
String::from("Kernel Version"),
util::exec_cmd("uname", &["-v"])
.unwrap_or_else(|_| String::from("?")),
],
vec![
String::from("Kernel Platform"),
util::exec_cmd("uname", &["-om"])
.unwrap_or_else(|_| String::from("?")),
],
]
.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_info() {
let mut kernel_info = KernelInfo::default();
for _x in 0..kernel_info.uname_output.len() + 1 {
kernel_info.next();
}
assert_eq!("Kernel Release", kernel_info.current_info[0]);
assert_eq!(
util::exec_cmd("uname", &["-srn"]).unwrap(),
kernel_info.current_info[1]
);
}
}
kmon-1.7.1/src/kernel/lkm.rs 0000644 0000000 0000000 00000024245 10461020230 0014005 0 ustar 0000000 0000000 use crate::app::ScrollDirection;
use crate::kernel::cmd::{Command, ModuleCommand};
use crate::style::{Style, StyledText, Symbol};
use crate::util;
use bytesize::ByteSize;
use clap::ArgMatches;
use ratatui::text::{Line, Span, Text};
use std::error::Error;
use std::slice::Iter;
/// Type of the sorting of module list
#[derive(Clone, Copy, Debug)]
enum SortType {
None,
Size,
Name,
Dependent,
}
impl SortType {
/// Return iterator for the sort types.
#[allow(dead_code)]
pub fn iter() -> Iter<'static, SortType> {
[
SortType::None,
SortType::Size,
SortType::Name,
SortType::Dependent,
]
.iter()
}
}
/// Listing properties of module list
pub struct ListArgs {
sort: SortType,
reverse: bool,
regex: bool,
}
impl ListArgs {
/// Create a new list arguments instance.
pub fn new(args: &ArgMatches) -> Self {
let mut sort_type = SortType::None;
if let Some(("sort", matches)) = args.subcommand() {
if matches.get_flag("size") {
sort_type = SortType::Size;
} else if matches.get_flag("dependent") {
sort_type = SortType::Dependent;
} else {
sort_type = SortType::Name;
}
}
Self {
sort: sort_type,
reverse: args.try_get_one::("reverse").ok().flatten()
== Some(&true),
regex: args.try_get_one::("regex").ok().flatten() == Some(&true),
}
}
pub fn regex(&self) -> bool {
self.regex
}
}
/// Loadable kernel modules
pub struct KernelModules<'a> {
pub default_list: Vec>,
pub list: Vec>,
pub current_name: String,
pub current_info: StyledText<'a>,
pub command: ModuleCommand,
pub index: usize,
pub info_scroll_offset: usize,
pub style: Style,
pub args: ListArgs,
}
impl KernelModules<'_> {
/// Create a new kernel modules instance.
pub fn new(args: ListArgs, style: Style) -> Self {
let mut kernel_modules = Self {
default_list: Vec::new(),
list: Vec::new(),
current_name: String::new(),
current_info: StyledText::default(),
command: ModuleCommand::None,
index: 0,
info_scroll_offset: 0,
args,
style,
};
if let Err(e) = kernel_modules.refresh() {
eprintln!("{e}");
}
kernel_modules
}
/// Parse kernel modules from '/proc/modules'.
pub fn refresh(&mut self) -> Result<(), Box> {
let mut module_list: Vec> = Vec::new();
// Set the command for reading kernel modules and execute it.
let mut module_read_cmd = String::from("cat /proc/modules");
match self.args.sort {
SortType::Size => module_read_cmd += " | sort -n -r -t ' ' -k2",
SortType::Name => module_read_cmd += " | sort -t ' ' -k1",
SortType::Dependent => module_read_cmd += " | sort -n -r -t ' ' -k3",
_ => {}
}
let modules_content = util::exec_cmd("sh", &["-c", &module_read_cmd])?;
// Parse content for module name, size and related information.
for line in modules_content.lines() {
let columns: Vec<&str> = line.split_whitespace().collect();
let mut module_name = format!(" {}", columns[0]);
if columns.len() >= 7 {
module_name.push(' ');
module_name.push_str(columns[6]);
}
let mut used_modules = format!("{} {}", columns[2], columns[3]);
if used_modules.ends_with(',') {
used_modules.pop();
}
let module_size =
ByteSize::b(columns[1].parse().unwrap_or(0)).to_string_as(true);
module_list.push(vec![module_name, module_size, used_modules]);
}
// Reverse the kernel modules if the argument is provided.
if self.args.reverse {
module_list.reverse();
}
self.default_list.clone_from(&module_list);
self.list = module_list;
self.scroll_list(ScrollDirection::Top);
Ok(())
}
/// Get the current command using current module name.
pub fn get_current_command(&self) -> Command {
self.command.get(&self.current_name)
}
/// Set the current module command and show confirmation message.
pub fn set_current_command(
&mut self,
module_command: ModuleCommand,
command_name: String,
) {
if !command_name.contains(' ') && !self.current_name.starts_with('!') {
if !command_name.is_empty() {
self.current_name = command_name;
}
self.command = module_command;
self.current_info.set(
Text::from({
let mut spans = vec![
Line::from(Span::styled(
"Execute the following command? [y/N]:",
self.style.colored,
)),
Line::from(Span::styled(
self.get_current_command().cmd,
self.style.default,
)),
Line::default(),
];
spans.append(
&mut Text::styled(
self.get_current_command().desc,
self.style.colored,
)
.lines,
);
spans
}),
self.get_current_command().cmd,
);
self.info_scroll_offset = 0;
}
}
/// Execute the current module command.
pub fn execute_command(&mut self) -> bool {
let mut command_executed = false;
if !self.command.is_none() {
match util::exec_cmd("sh", &["-c", &self.get_current_command().cmd]) {
Ok(_) => command_executed = true,
Err(e) => {
self.current_info.set(
Text::from({
let mut spans = vec![
Line::from(Span::styled(
"Failed to execute command:",
self.style.colored,
)),
Line::from(Span::styled(
format!("'{}'", self.get_current_command().cmd),
self.style.default,
)),
Line::default(),
];
spans.append(
&mut Text::styled(e.to_string(), self.style.default)
.lines,
);
spans
}),
format!(
"Execution Error\n'{}'\n{}",
self.get_current_command().cmd,
e
),
);
self.current_name =
format!("!Error{}", self.style.unicode.get(Symbol::NoEntry));
}
}
self.command = ModuleCommand::None;
}
command_executed
}
/// Cancel the execution of the current command.
pub fn cancel_execution(&mut self) -> bool {
if !self.command.is_none() {
self.command = ModuleCommand::None;
if self.index != 0 {
self.index -= 1;
self.scroll_list(ScrollDirection::Down);
} else {
self.index += 1;
self.scroll_list(ScrollDirection::Up);
};
true
} else {
false
}
}
/// Scroll to the position of used module at given index.
pub fn show_used_module(&mut self, mod_index: usize) {
if let Some(used_module) = self.list[self.index][2]
.split(' ')
.collect::>()
.get(1)
.unwrap_or(&"")
.split(',')
.collect::>()
.get(mod_index)
{
if let Some(v) = self
.list
.iter()
.position(|module| module[0] == format!(" {used_module}"))
{
match v {
0 => {
self.index = v + 1;
self.scroll_list(ScrollDirection::Up);
}
v if v > 0 => {
self.index = v - 1;
self.scroll_list(ScrollDirection::Down);
}
_ => {}
}
}
}
}
/// Scroll module list up/down and select module.
pub fn scroll_list(&mut self, direction: ScrollDirection) {
self.info_scroll_offset = 0;
if self.list.is_empty() {
self.index = 0;
} else {
// Scroll module list.
match direction {
ScrollDirection::Up => self.previous_module(),
ScrollDirection::Down => self.next_module(),
ScrollDirection::Top => self.index = 0,
ScrollDirection::Bottom => self.index = self.list.len() - 1,
_ => {}
}
// Set current module name.
self.current_name = self.list[self.index][0]
.split_whitespace()
.next()
.unwrap_or("?")
.trim()
.to_string();
// Execute 'modinfo' and add style to its output.
self.current_info.stylize_data(
Box::leak(
util::exec_cmd("modinfo", &[&self.current_name])
.unwrap_or_else(|_| {
String::from("module information not available")
})
.replace("signature: ", "signature: \n")
.into_boxed_str(),
),
":",
self.style.clone(),
);
// Clear the current command.
if !self.command.is_none() {
self.command = ModuleCommand::None;
}
}
}
/// Select the next module.
pub fn next_module(&mut self) {
self.index += 1;
if self.index > self.list.len() - 1 {
self.index = 0;
}
}
/// Select the previous module.
pub fn previous_module(&mut self) {
if self.index > 0 {
self.index -= 1;
} else {
self.index = self.list.len() - 1;
}
}
/// Scroll the module information text up/down.
pub fn scroll_mod_info(
&mut self,
direction: ScrollDirection,
smooth_scroll: bool,
) {
let scroll_amount = if smooth_scroll { 1 } else { 2 };
match direction {
ScrollDirection::Up => {
if self.info_scroll_offset > scroll_amount - 1 {
self.info_scroll_offset -= scroll_amount;
}
}
ScrollDirection::Down => {
if self.current_info.lines() > 0 {
self.info_scroll_offset += scroll_amount;
self.info_scroll_offset %= self.current_info.lines() * 2;
}
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_modules() {
let args = ArgMatches::default();
let mut list_args = ListArgs::new(&args);
list_args.sort = SortType::Size;
list_args.reverse = true;
let mut kernel_modules = KernelModules::new(list_args, Style::new(&args));
for sort_type in SortType::iter().rev().chain(SortType::iter()) {
kernel_modules.args.sort = *sort_type;
kernel_modules.refresh();
}
for direction in ScrollDirection::iter().rev().chain(ScrollDirection::iter())
{
kernel_modules.show_used_module(0);
kernel_modules.scroll_list(*direction);
kernel_modules
.scroll_mod_info(*direction, *direction == ScrollDirection::Up);
}
kernel_modules.scroll_list(ScrollDirection::Down);
assert_eq!(0, kernel_modules.index);
kernel_modules.scroll_list(ScrollDirection::Up);
assert_eq!(kernel_modules.default_list.len() - 1, kernel_modules.index);
assert_ne!(0, kernel_modules.default_list.len());
assert_ne!(0, kernel_modules.current_name.len());
assert_ne!(0, kernel_modules.current_info.lines());
kernel_modules
.set_current_command(ModuleCommand::Load, String::from("test"));
assert_eq!("test", kernel_modules.current_name);
assert!(!kernel_modules.execute_command());
kernel_modules.set_current_command(ModuleCommand::Load, String::new());
kernel_modules.scroll_list(ScrollDirection::Top);
for command in [
ModuleCommand::Unload,
ModuleCommand::Blacklist,
ModuleCommand::None,
] {
kernel_modules.set_current_command(command, String::new());
assert_eq!(!command.is_none(), kernel_modules.cancel_execution());
}
}
}
kmon-1.7.1/src/kernel/log.rs 0000644 0000000 0000000 00000005173 10461020230 0014002 0 ustar 0000000 0000000 use crate::app::ScrollDirection;
use crate::util;
use std::fmt::Write as _;
/// Kernel activity logs
#[derive(Clone, Debug, Default)]
pub struct KernelLogs {
pub output: String,
pub selected_output: String,
last_line: String,
crop_offset: usize,
pub index: usize,
}
impl KernelLogs {
/// Update the output variable value if 'dmesg' logs changed.
pub fn update(&mut self) -> bool {
self.output = util::exec_cmd(
"dmesg",
&["--kernel", "--human", "--ctime", "--color=never"],
)
.unwrap_or_else(|_| String::from("failed to retrieve dmesg output"));
let logs_updated =
self.output.lines().next_back().unwrap_or_default() != self.last_line;
self.last_line = self
.output
.lines()
.next_back()
.unwrap_or_default()
.to_string();
logs_updated
}
/// Refresh the kernel logs.
pub fn refresh(&mut self) {
self.last_line = String::new();
self.index = 0;
self.crop_offset = 0;
self.update();
}
/// Select a part of the output depending on the area properties.
pub fn select(&mut self, area_height: u16, area_sub: u16) -> &str {
self.selected_output = self
.output
.lines()
.map(|line| match line.char_indices().nth(self.crop_offset) {
Some((pos, _)) => &line[pos..],
None => "",
})
.skip(
area_height
.checked_sub(area_sub)
.and_then(|height| {
(self.output.lines().count() - self.index)
.checked_sub(height as usize)
})
.unwrap_or(0),
)
.fold(String::new(), |mut s, i| {
let _ = writeln!(s, "{i}");
s
});
&self.selected_output
}
/// Scroll the kernel logs up/down.
pub fn scroll(&mut self, direction: ScrollDirection, smooth_scroll: bool) {
let scroll_amount = if smooth_scroll { 1 } else { 3 };
match direction {
ScrollDirection::Up => {
if self.index + scroll_amount <= self.output.lines().count() {
self.index += scroll_amount;
}
}
ScrollDirection::Down => {
if self.index > scroll_amount - 1 {
self.index -= scroll_amount;
} else {
self.index = 0;
}
}
ScrollDirection::Left => {
self.crop_offset = self.crop_offset.saturating_sub(10)
}
ScrollDirection::Right => {
self.crop_offset = self.crop_offset.checked_add(10).unwrap_or(0)
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kernel_logs() {
let mut kernel_logs = KernelLogs::default();
for direction in ScrollDirection::iter().rev().chain(ScrollDirection::iter())
{
kernel_logs.scroll(*direction, *direction == ScrollDirection::Top);
}
assert!(kernel_logs.update());
assert_ne!(0, kernel_logs.output.lines().count());
assert_ne!(0, kernel_logs.select(10, 2).len());
}
}
kmon-1.7.1/src/kernel/mod.rs 0000644 0000000 0000000 00000001352 10461020230 0013773 0 ustar 0000000 0000000 pub mod cmd;
pub mod info;
pub mod lkm;
pub mod log;
use crate::style::Style;
use clap::ArgMatches;
use info::KernelInfo;
use lkm::{KernelModules, ListArgs};
use log::KernelLogs;
/// Kernel struct for logs, information and modules
pub struct Kernel {
pub logs: KernelLogs,
pub info: KernelInfo,
pub modules: KernelModules<'static>,
}
impl Kernel {
/// Create a new kernel instance.
pub fn new(args: &ArgMatches) -> Self {
Self {
logs: KernelLogs::default(),
info: KernelInfo::default(),
modules: KernelModules::new(ListArgs::new(args), Style::new(args)),
}
}
/// Refresh kernel logs, modules and information.
pub fn refresh(&mut self) {
self.logs.refresh();
self.info.refresh();
let _ = self.modules.refresh();
}
}
kmon-1.7.1/src/lib.rs 0000644 0000000 0000000 00000042053 10461020230 0012505 0 ustar 0000000 0000000 #![allow(clippy::tabs_in_doc_comments)]
pub mod app;
pub mod event;
pub mod kernel;
pub mod widgets;
#[macro_use]
pub mod util;
pub mod args;
pub mod style;
use crate::app::{App, Block, InputMode, ScrollDirection};
use crate::kernel::cmd::ModuleCommand;
use crate::kernel::Kernel;
use enum_iterator::Sequence;
use event::{Event, Events};
use ratatui::backend::Backend;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::Terminal;
use std::error::Error;
use termion::event::Key;
use unicode_width::UnicodeWidthStr;
/// Configure the terminal and draw its widgets.
pub fn start_tui(
mut terminal: Terminal,
mut kernel: Kernel,
events: &Events,
) -> Result<(), Box>
where
B: Backend,
{
// Configure the application.
let mut app = App::new(Block::ModuleTable, kernel.modules.style.clone());
// Draw terminal and render the widgets.
loop {
terminal.draw(|frame| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage(100 - app.block_size.activities),
Constraint::Percentage(app.block_size.activities),
]
.as_ref(),
)
.split(frame.area());
{
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage(100 - app.block_size.info),
Constraint::Percentage(app.block_size.info),
]
.as_ref(),
)
.split(chunks[0]);
{
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[Constraint::Length(3), Constraint::Percentage(100)]
.as_ref(),
)
.split(chunks[0]);
{
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage(app.block_size.input),
Constraint::Percentage(
100 - app.block_size.input,
),
]
.as_ref(),
)
.split(chunks[0]);
app.draw_user_input(frame, chunks[0], &events.tx);
app.draw_kernel_info(
frame,
chunks[1],
&kernel.info.current_info,
);
}
if app.block_size.info != 100 {
app.draw_dynamic_block(frame, chunks[1], &mut kernel);
} else {
app.block_index += 1;
}
}
app.draw_dynamic_block(frame, chunks[1], &mut kernel);
}
app.draw_dynamic_block(frame, chunks[1], &mut kernel);
if !app.input_mode.is_none() {
frame.set_cursor_position((1 + app.input_query.width() as u16, 1));
}
})?;
// Handle terminal events.
match events.rx.recv()? {
// Key input events.
Event::Input(input) => {
let mut hide_options = true;
if app.input_mode.is_none() {
// Default input mode.
match input {
// Quit.
Key::Char('q')
| Key::Char('Q')
| Key::Ctrl('c')
| Key::Ctrl('d')
| Key::Esc => {
if app.show_options {
app.show_options = false;
} else {
break;
}
}
// Refresh.
Key::Char('r') | Key::Char('R') | Key::F(5) => {
app.refresh();
kernel.refresh();
}
// Show help message.
Key::Char('?') | Key::F(1) => {
app.show_help_message(&mut kernel.modules);
}
Key::Char('m') | Key::Char('o') => {
app.show_options = true;
hide_options = false;
}
// Scroll the selected block up.
Key::Up
| Key::Char('k')
| Key::Char('K')
| Key::Alt('k')
| Key::Alt('K') => {
if app.show_options {
app.options.previous();
continue;
} else {
app.options.state.select(Some(0));
}
match app.selected_block {
Block::ModuleTable => {
kernel.modules.scroll_list(ScrollDirection::Up)
}
Block::ModuleInfo => kernel.modules.scroll_mod_info(
ScrollDirection::Up,
input == Key::Alt('k') || input == Key::Alt('K'),
),
Block::Activities => {
kernel.logs.scroll(
ScrollDirection::Up,
input == Key::Alt('k')
|| input == Key::Alt('K'),
);
}
_ => {}
}
}
// Scroll the selected block down.
Key::Down
| Key::Char('j')
| Key::Char('J')
| Key::Alt('j')
| Key::Alt('J') => {
if app.show_options {
app.options.next();
continue;
} else {
app.options.state.select(Some(0));
}
match app.selected_block {
Block::ModuleTable => {
kernel.modules.scroll_list(ScrollDirection::Down)
}
Block::ModuleInfo => kernel.modules.scroll_mod_info(
ScrollDirection::Down,
input == Key::Alt('j') || input == Key::Alt('J'),
),
Block::Activities => {
kernel.logs.scroll(
ScrollDirection::Down,
input == Key::Alt('j')
|| input == Key::Alt('J'),
);
}
_ => {}
}
}
// Select the next terminal block.
Key::Left | Key::Char('h') | Key::Char('H') => {
app.selected_block = match app.selected_block.previous()
{
Some(v) => v,
None => Block::last().unwrap(),
}
}
// Select the previous terminal block.
Key::Right | Key::Char('l') | Key::Char('L') => {
app.selected_block = match app.selected_block.next() {
Some(v) => v,
None => Block::first().unwrap(),
}
}
// Expand the selected block.
Key::Alt('e') => {
let block_size = app.block_size();
if *block_size < 95 {
*block_size += 5;
} else {
*block_size = 100;
}
}
// Shrink the selected block.
Key::Alt('s') => {
let block_size = app.block_size();
*block_size =
(*block_size).checked_sub(5).unwrap_or_default()
}
// Change the block position.
Key::Ctrl('x') => {
if app.block_index == 2 {
app.block_index = 0;
} else {
app.block_index += 1;
}
}
// Scroll to the top of the module list.
Key::Ctrl('t') | Key::Home => {
app.options.state.select(Some(0));
app.selected_block = Block::ModuleTable;
kernel.modules.scroll_list(ScrollDirection::Top)
}
// Scroll to the bottom of the module list.
Key::Ctrl('b') | Key::End => {
app.options.state.select(Some(0));
app.selected_block = Block::ModuleTable;
kernel.modules.scroll_list(ScrollDirection::Bottom)
}
// Scroll kernel activities up.
Key::PageUp => {
app.selected_block = Block::Activities;
kernel.logs.scroll(ScrollDirection::Up, false);
}
// Scroll kernel activities down.
Key::PageDown => {
app.selected_block = Block::Activities;
kernel.logs.scroll(ScrollDirection::Down, false);
}
// Scroll kernel activities left.
Key::Alt('h') | Key::Alt('H') => {
app.selected_block = Block::Activities;
kernel.logs.scroll(ScrollDirection::Left, false);
}
// Scroll kernel activities right.
Key::Alt('l') | Key::Alt('L') => {
app.selected_block = Block::Activities;
kernel.logs.scroll(ScrollDirection::Right, false);
}
// Scroll module information up.
Key::Char('<') | Key::Alt(' ') => {
app.selected_block = Block::ModuleInfo;
kernel
.modules
.scroll_mod_info(ScrollDirection::Up, false)
}
// Scroll module information down.
Key::Char('>') | Key::Char(' ') => {
app.selected_block = Block::ModuleInfo;
kernel
.modules
.scroll_mod_info(ScrollDirection::Down, false)
}
// Show the next kernel information.
Key::Char('\\') | Key::Char('\t') | Key::BackTab => {
kernel.info.next();
}
// Display the dependent modules.
Key::Char('d') | Key::Alt('d') => {
app.show_dependent_modules(&mut kernel.modules);
}
// Clear the kernel ring buffer.
Key::Ctrl('l')
| Key::Ctrl('u')
| Key::Alt('c')
| Key::Alt('C') => {
kernel.modules.set_current_command(
ModuleCommand::Clear,
String::new(),
);
}
// Unload kernel module.
Key::Char('u')
| Key::Char('U')
| Key::Char('-')
| Key::Backspace
| Key::Ctrl('h') => {
kernel.modules.set_current_command(
ModuleCommand::Unload,
String::new(),
);
}
// Blacklist kernel module.
Key::Char('x')
| Key::Char('X')
| Key::Char('b')
| Key::Char('B')
| Key::Delete => {
kernel.modules.set_current_command(
ModuleCommand::Blacklist,
String::new(),
);
}
// Reload kernel module.
Key::Ctrl('r')
| Key::Ctrl('R')
| Key::Alt('r')
| Key::Alt('R') => {
kernel.modules.set_current_command(
ModuleCommand::Reload,
String::new(),
);
}
// Execute the current command.
Key::Char('y') | Key::Char('Y') => {
if kernel.modules.execute_command() {
events
.tx
.send(Event::Input(Key::Char('r')))
.unwrap();
}
}
// Cancel the execution of current command.
Key::Char('n') | Key::Char('N') => {
if kernel.modules.cancel_execution() {
app.selected_block = Block::ModuleTable;
}
}
// Copy the data in selected block to clipboard.
Key::Char('c') | Key::Char('C') => {
app.set_clipboard_contents(match app.selected_block {
Block::ModuleTable => &kernel.modules.current_name,
Block::ModuleInfo => {
&kernel.modules.current_info.raw_text
}
Block::Activities => {
kernel.logs.selected_output.trim()
}
_ => "",
});
}
// Paste the clipboard contents and switch to search mode.
Key::Char('v') | Key::Ctrl('V') | Key::Ctrl('v') => {
let clipboard_contents = app.get_clipboard_contents();
app.input_query += &clipboard_contents;
events.tx.send(Event::Input(Key::Char('\n'))).unwrap();
kernel.modules.index = 0;
}
// User input mode.
Key::Char('\n')
| Key::Char('s')
| Key::Char('S')
| Key::Char('i')
| Key::Char('I')
| Key::Char('+')
| Key::Char('/')
| Key::Insert => {
if input == Key::Char('\n') && app.show_options {
if let Ok(command) = ModuleCommand::try_from(
app.options
.selected()
.map(|(v, _)| v.to_string())
.unwrap_or_default(),
) {
if command == ModuleCommand::Load {
events
.tx
.send(Event::Input(Key::Char('+')))
.unwrap();
} else {
kernel.modules.set_current_command(
command,
String::new(),
);
}
} else {
match app
.options
.selected()
.map(|(v, _)| v.as_ref())
{
Some("dependent") => {
app.show_dependent_modules(
&mut kernel.modules,
);
}
Some("copy") => app.set_clipboard_contents(
&kernel.modules.current_name,
),
_ => {}
}
}
} else {
app.selected_block = Block::UserInput;
app.input_mode = match input {
Key::Char('+')
| Key::Char('i')
| Key::Char('I')
| Key::Insert => InputMode::Load,
_ => InputMode::Search,
};
if input != Key::Char('\n') {
app.input_query = String::new();
}
}
}
// Other character input.
Key::Char(v) => {
// Check if input is a number except zero.
let index = v.to_digit(10).unwrap_or(0);
// Show the used module info at given index.
if index != 0 && !kernel.modules.list.is_empty() {
app.selected_block = Block::ModuleTable;
kernel.modules.show_used_module(index as usize - 1);
}
}
_ => {}
}
} else {
// User input mode.
match input {
// Quit with ctrl-d.
Key::Ctrl('d') => {
break;
}
// Switch to the previous input mode.
Key::Up => {
app.input_mode = match app.input_mode.previous() {
Some(v) => v,
None => InputMode::last().unwrap(),
};
if app.input_mode.is_none() {
app.input_mode = InputMode::last().unwrap();
}
app.input_query = String::new();
}
// Switch to the next input mode.
Key::Down => {
app.input_mode = match app.input_mode.next() {
Some(v) => v,
None => InputMode::first()
.and_then(|v| v.next())
.unwrap(),
};
app.input_query = String::new();
}
// Copy input query to the clipboard.
Key::Ctrl('c') => {
let query = app.input_query.clone();
app.set_clipboard_contents(&query);
}
// Paste the clipboard contents.
Key::Ctrl('v') => {
let clipboard_contents = app.get_clipboard_contents();
app.input_query += &clipboard_contents;
}
// Exit user input mode.
Key::Char('\n')
| Key::Char('\t')
| Key::F(1)
| Key::Right
| Key::Left => {
// Select the next eligible block for action.
app.selected_block = match input {
Key::Left => match app.selected_block.previous() {
Some(v) => v,
None => Block::last().unwrap(),
},
Key::Char('\n') => match app.input_mode {
InputMode::Load
if !app.input_query.is_empty() =>
{
Block::ModuleInfo
}
_ => Block::ModuleTable,
},
_ => Block::ModuleTable,
};
// Show the first modules information if the search mode is set.
if app.input_mode == InputMode::Search
&& kernel.modules.index == 0
{
kernel.modules.scroll_list(ScrollDirection::Top);
// Load kernel module.
} else if app.input_mode == InputMode::Load
&& !app.input_query.is_empty()
{
kernel.modules.set_current_command(
ModuleCommand::Load,
app.input_query,
);
app.input_query = String::new();
}
// Set the input mode flag.
app.input_mode = InputMode::None;
}
// Append character to input query.
Key::Char(c) => {
app.input_query.push(c);
kernel.modules.index = 0;
}
// Delete the last character from input query.
Key::Backspace | Key::Ctrl('h') => {
app.input_query.pop();
kernel.modules.index = 0;
}
// Clear the input query.
Key::Delete | Key::Ctrl('l') => {
app.input_query = String::new();
kernel.modules.index = 0;
}
// Clear the input query and exit user input mode.
Key::Esc => {
events.tx.send(Event::Input(Key::Delete)).unwrap();
events.tx.send(Event::Input(Key::Char('\n'))).unwrap();
}
_ => {}
}
}
if hide_options {
app.show_options = false;
}
}
// Kernel events.
Event::Kernel(logs) => {
kernel.logs.output = logs;
}
_ => {}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use clap::ArgMatches;
use ratatui::backend::TestBackend;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::Duration;
#[test]
fn test_tui() -> Result<(), Box> {
let args = ArgMatches::default();
let kernel = Kernel::new(&args);
let events = Events::new(100, &kernel.logs);
let tx = events.tx.clone();
thread::spawn(move || {
// Test the general keys.
for key in [
Key::Char('?'),
Key::Ctrl('t'),
Key::Ctrl('b'),
Key::Alt('e'),
Key::Alt('s'),
Key::Ctrl('x'),
Key::Ctrl('x'),
Key::Ctrl('x'),
Key::Char('x'),
Key::Char('n'),
Key::Char('d'),
Key::Ctrl('l'),
Key::Char('u'),
Key::Ctrl('r'),
Key::Char('y'),
Key::PageUp,
Key::PageDown,
Key::Alt('l'),
Key::Alt('h'),
Key::Char('<'),
Key::Char('>'),
Key::Char('\t'),
Key::Char('m'),
Key::Down,
Key::Char('\n'),
] {
send_key(&tx, key);
}
send_key(&tx, Key::Char('r'));
// Test the switch keys.
for arrow_key in [Key::Right, Key::Left] {
for selected_key in [arrow_key; Block::CARDINALITY] {
send_key(&tx, selected_key);
for key in [
Key::Up,
Key::Down,
Key::Down,
Key::Up,
Key::Char('c'),
Key::Char('~'),
Key::Char('1'),
] {
send_key(&tx, key);
}
}
}
// Test the input mode keys.
for key in [
Key::Char('v'),
Key::Delete,
Key::Char('~'),
Key::Backspace,
Key::Ctrl('c'),
Key::Ctrl('v'),
Key::Char('a'),
Key::Char('\n'),
Key::Char('\n'),
Key::Char('?'),
Key::Char('\n'),
Key::Esc,
Key::Char('i'),
Key::Char('x'),
Key::Char('\n'),
] {
send_key(&tx, key);
}
// Exit.
send_key(&tx, Key::Esc)
});
start_tui(Terminal::new(TestBackend::new(20, 10))?, kernel, &events)
}
// Try to send a key event until Sender succeeds.
fn send_key(tx: &Sender>, key: Key) {
let mut x = true;
while x {
x = tx.send(Event::Input(key)).is_err();
thread::sleep(Duration::from_millis(10));
}
}
}
kmon-1.7.1/src/main.rs 0000644 0000000 0000000 00000001525 10461020230 0012662 0 ustar 0000000 0000000 use kmon::args;
use kmon::event::Events;
use kmon::kernel::Kernel;
use kmon::util;
use ratatui::backend::TermionBackend;
use ratatui::Terminal;
use std::error::Error;
use std::io::stdout;
use termion::input::MouseTerminal;
use termion::raw::IntoRawMode;
use termion::screen::IntoAlternateScreen;
/// Entry point.
fn main() -> Result<(), Box> {
let args = args::get_args().get_matches();
let kernel = Kernel::new(&args);
let events = Events::new(
args.get_one::("rate")
.unwrap()
.parse::()
.unwrap_or(250),
&kernel.logs,
);
if !cfg!(test) {
util::setup_panic_hook()?;
let stdout = stdout().into_raw_mode()?.into_alternate_screen()?;
let stdout = MouseTerminal::from(stdout);
let backend = TermionBackend::new(stdout);
kmon::start_tui(Terminal::new(backend)?, kernel, &events)
} else {
Ok(())
}
}
kmon-1.7.1/src/style.rs 0000644 0000000 0000000 00000012635 10461020230 0013102 0 ustar 0000000 0000000 use clap::ArgMatches;
use colorsys::Rgb;
use ratatui::style::{Color, Modifier, Style as TuiStyle};
use ratatui::text::{Line, Span, Text};
use std::collections::HashMap;
/// Unicode symbol
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Symbol {
None,
Blank,
Gear,
Cloud,
Anchor,
Helmet,
CircleX,
SquareX,
NoEntry,
FuelPump,
Magnifier,
HighVoltage,
LeftBracket,
RightBracket,
HistoricSite,
}
/// Supported Unicode symbols
#[derive(Clone, Debug)]
pub struct Unicode<'a> {
symbols: HashMap,
replace: bool,
}
impl Unicode<'_> {
/// Create a new Unicode instance.
pub fn new(replace: bool) -> Self {
Self {
symbols: map! {
Symbol::None => &["", ""],
Symbol::Blank => &["\u{2800} ", "\u{2800} "],
Symbol::Gear => &[" \u{2699} ", ""],
Symbol::Cloud => &[" \u{26C5} ", ""],
Symbol::Anchor => &[" \u{2693}", ""],
Symbol::Helmet => &[" \u{26D1} ", ""],
Symbol::CircleX => &[" \u{1F167} ", ""],
Symbol::SquareX => &[" \u{1F187} ", ""],
Symbol::NoEntry => &[" \u{26D4}", ""],
Symbol::FuelPump => &[" \u{26FD}", ""],
Symbol::Magnifier => &[" \u{1F50D}", ""],
Symbol::HighVoltage => &[" \u{26A1}", ""],
Symbol::LeftBracket => &["\u{2997}", "("],
Symbol::RightBracket => &["\u{2998}", ")"],
Symbol::HistoricSite => &[" \u{26EC} ", ""]
},
replace,
}
}
/// Get string from a Unicode symbol.
pub fn get(&self, symbol: Symbol) -> &str {
self.symbols[&symbol][self.replace as usize]
}
}
/// Style properties
#[derive(Clone, Debug)]
pub struct Style {
pub default: TuiStyle,
pub bold: TuiStyle,
pub colored: TuiStyle,
pub unicode: Unicode<'static>,
}
impl Style {
/// Create a new style instance from given arguments.
pub fn new(args: &ArgMatches) -> Self {
let mut default = TuiStyle::reset();
if let Ok(true) = args.try_contains_id("accent-color") {
default =
default.fg(Self::get_color(args, "accent-color", Color::White));
}
Self {
default,
bold: TuiStyle::reset().add_modifier(Modifier::BOLD),
colored: TuiStyle::reset().fg(Self::get_color(
args,
"color",
Color::DarkGray,
)),
unicode: Unicode::new(
args.try_get_one::("unicode").ok().flatten() == Some(&false),
),
}
}
/// Parse a color value from arguments.
fn get_color(args: &ArgMatches, arg_name: &str, default_color: Color) -> Color {
let colors = map![
"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"gray" => Color::Gray,
"darkgray" => Color::DarkGray,
"lightred" => Color::LightRed,
"lightgreen" => Color::LightGreen,
"lightyellow" => Color::LightYellow,
"lightblue" => Color::LightBlue,
"lightmagenta" => Color::LightMagenta,
"lightcyan" => Color::LightCyan,
"white" => Color::White
];
match args.try_get_one::(arg_name) {
Ok(Some(v)) => *colors.get::(&v.to_lowercase()).unwrap_or({
if let Ok(rgb) = Rgb::from_hex_str(&format!("#{v}")) {
Box::leak(Box::new(Color::Rgb(
rgb.red() as u8,
rgb.green() as u8,
rgb.blue() as u8,
)))
} else {
&default_color
}
}),
_ => default_color,
}
}
}
/// Styled text that has raw and style parts
#[derive(Debug, Default)]
pub struct StyledText<'a> {
pub raw_text: String,
pub styled_text: Text<'a>,
}
impl<'a> StyledText<'a> {
/// Get a vector of Text widget from styled text.
pub fn get(&'a self) -> Text<'a> {
if self.styled_text.lines.is_empty() {
Text::raw(&self.raw_text)
} else {
self.styled_text.clone()
}
}
/// Set a styled text.
pub fn set(&mut self, text: Text<'static>, placeholder: String) {
self.styled_text = text;
self.raw_text = placeholder;
}
/// Add style to given text depending on a delimiter.
pub fn stylize_data(
&mut self,
text: &'a str,
delimiter: &str,
style: Style,
) -> Text<'a> {
self.styled_text = Text::default();
self.raw_text = text.to_string();
for line in text.lines() {
let data = line.split(delimiter).collect::>();
if data.len() > 1 && data[0].trim().len() > 2 {
self.styled_text.lines.push(Line::from(vec![
Span::styled(format!("{}{}", data[0], delimiter), style.colored),
Span::styled(data[1..data.len()].join(delimiter), style.default),
]));
} else {
self.styled_text
.lines
.push(Line::from(Span::styled(line, style.default)))
}
}
self.styled_text.clone()
}
/// Return the line count of styled text.
pub fn lines(&self) -> usize {
if self.styled_text.lines.is_empty() {
self.raw_text.lines().count()
} else {
self.styled_text.lines.len()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::ArgMatches;
#[test]
fn test_style() {
let args = ArgMatches::default();
let style = Style::new(&args);
let mut styled_text = StyledText::default();
styled_text.set(
Text::styled("styled\ntext", style.colored),
String::from("test"),
);
assert_eq!(
Text::styled("styled\ntext", style.colored),
styled_text.get()
);
assert_eq!(2, styled_text.lines());
assert_eq!("test", styled_text.raw_text);
}
#[test]
fn test_unicode() {
let mut unicode = Unicode::new(true);
for symbol in unicode.symbols.clone() {
if symbol.0 != Symbol::Blank {
assert!(symbol.1[1].len() < 2)
}
}
unicode.replace = false;
for symbol in unicode.symbols {
if symbol.0 != Symbol::None {
assert_ne!("", symbol.1[0]);
}
}
}
}
kmon-1.7.1/src/util.rs 0000644 0000000 0000000 00000005723 10461020230 0012717 0 ustar 0000000 0000000 use std::error::Error;
use std::io::{self, Write};
use std::panic;
use std::process::Command;
use termion::raw::IntoRawMode;
/// Macro for concise initialization of hashmap
macro_rules! map {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $val); )*
map
}}
}
/// Array of the key bindings
pub const KEY_BINDINGS: &[(&str, &str)] = &[
("'?', f1", "help"),
("right/left, h/l", "switch between blocks"),
("up/down, k/j, alt-k/j", "scroll up/down [selected block]"),
("pgup/pgdown", "scroll up/down [kernel activities]"),
(">", "scroll up/down [module information]"),
("alt-h/l", "scroll right/left [kernel activities]"),
("ctrl-t/b, home/end", "scroll to top/bottom [module list]"),
("alt-e/s", "expand/shrink the selected block"),
("ctrl-x", "change the block position"),
("ctrl-l/u, alt-c", "clear the kernel ring buffer"),
("d, alt-d", "show the dependent modules"),
("1..9", "jump to the dependent module"),
("\\, tab, backtab", "show the next kernel information"),
("/, s, enter", "search a kernel module"),
("+, i, insert", "load a kernel module"),
("-, u, backspace", "unload the kernel module"),
("x, b, delete", "blacklist the kernel module"),
("ctrl-r, alt-r", "reload the kernel module"),
("m, o", "show the options menu"),
("y/n", "execute/cancel the command"),
("c/v", "copy/paste"),
("r, f5", "refresh"),
("q, ctrl-c/d, esc", "quit"),
];
/// Execute a operating system command and return its output.
pub fn exec_cmd(cmd: &str, cmd_args: &[&str]) -> Result {
match Command::new(cmd).args(cmd_args).output() {
Ok(output) => {
if output.status.success() {
Ok(String::from_utf8(output.stdout)
.expect("not UTF-8")
.trim_end()
.to_string())
} else {
Err(String::from_utf8(output.stderr)
.expect("not UTF-8")
.trim_end()
.to_string())
}
}
Err(e) => Err(e.to_string()),
}
}
/// Sets up the panic hook for the terminal.
///
/// See
pub fn setup_panic_hook() -> Result<(), Box> {
let raw_output = io::stdout().into_raw_mode()?;
raw_output.suspend_raw_mode()?;
let panic_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic| {
let panic_cleanup = || -> Result<(), Box> {
let mut output = io::stdout();
write!(
output,
"{}{}{}",
termion::clear::All,
termion::screen::ToMainScreen,
termion::cursor::Show
)?;
raw_output.suspend_raw_mode()?;
output.flush()?;
Ok(())
};
panic_cleanup().expect("failed to clean up for panic");
panic_hook(panic);
}));
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exec_cmd() {
assert_eq!("test", exec_cmd("printf", &["test"]).unwrap());
assert_eq!(
"true",
exec_cmd("sh", &["-c", "test 10 -eq 10 && echo 'true'"]).unwrap()
);
assert_eq!(
"err",
exec_cmd("cat", &["-x"]).unwrap_or(String::from("err"))
);
}
}
kmon-1.7.1/src/widgets.rs 0000644 0000000 0000000 00000003025 10461020230 0013401 0 ustar 0000000 0000000 use ratatui::widgets::ListState;
/// List widget with TUI controlled states.
#[derive(Debug)]
pub struct StatefulList {
/// List items (states).
pub items: Vec,
/// State that can be modified by TUI.
pub state: ListState,
}
impl StatefulList {
/// Constructs a new instance of `StatefulList`.
pub fn new(items: Vec, mut state: ListState) -> StatefulList {
state.select(Some(0));
Self { items, state }
}
/// Construct a new `StatefulList` with given items.
pub fn with_items(items: Vec) -> StatefulList {
Self::new(items, ListState::default())
}
/// Returns the selected item.
pub fn selected(&self) -> Option<&T> {
self.items.get(self.state.selected()?)
}
/// Selects the next item.
pub fn next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
/// Selects the previous item.
pub fn previous(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stateful_list() {
let mut list = StatefulList::with_items(vec!["data1", "data2", "data3"]);
list.state.select(Some(1));
assert_eq!(Some(&"data2"), list.selected());
list.next();
assert_eq!(Some(2), list.state.selected());
list.previous();
assert_eq!(Some(1), list.state.selected());
}
}