.
kmon-1.6.5/README.md 0000644 0000000 0000000 00000067655 10461020230 0012103 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 [tui-rs](https://github.com/fdehau/tui-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)
- [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.70.0`.
### Arch Linux
**kmon** can be installed from the Arch Linux [official repository](https://www.archlinux.org/packages/community/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
-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.

## 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
- [@orhunp\_](https://twitter.com/orhunp_) 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.6.5/src/app.rs 0000644 0000000 0000000 00000043352 10461020230 0012525 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 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.
*
* @return Iter
*/
#[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.
*
* @return bool
*/
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.
*
* @param Block
* @param Style
* @return App
*/
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.
*
* @param block
* @return TuiStyle
*/
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.
*
* @return u16
*/
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.
*
* @return contents
*/
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.
*
* @param 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.
*
* @param kernel_modules
*/
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.
*
* @param kernel_modules
*/
#[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.
*
* @param frame
* @param area
* @param kernel
*/
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.
*
* @param frame
* @param area
* @param tx
*/
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.
*
* @param frame
* @param area
* @param info
*/
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.
*
* @param frame
* @param area
* @param kernel_modules
*/
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();
if (self.input_mode == InputMode::None
|| self.input_mode == InputMode::Search)
&& !self.input_query.is_empty()
{
kernel_module_list.retain(|module| {
module[0]
.to_lowercase()
.contains(&self.input_query.to_lowercase())
});
}
/* 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] = format!("{}...", module[2]);
}
}
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.
*
* @param frame
* @param area
*/
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.
*
* @param frame
* @param area
* @param kernel_modules
*/
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.
*
* @param frame
* @param area
* @param kernel_logs
*/
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.6.5/src/args.rs 0000644 0000000 0000000 00000004771 10461020230 0012703 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.
*
* @return App
*/
pub fn get_args() -> App {
App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(format!(
"{} {}\n{}\n{}\n\n{}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_AUTHORS"),
env!("CARGO_PKG_DESCRIPTION"),
"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),
)
.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.6.5/src/event.rs 0000644 0000000 0000000 00000004717 10461020230 0013070 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.
*
* @param refresh_rate
* @param kernel_logs
* @return Events
*/
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.6.5/src/kernel/cmd.rs 0000644 0000000 0000000 00000013743 10461020230 0013771 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.
*
* @param command
* @param description
* @param command_title
* @return Command
*/
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.
*
* @param module_name
* @return Command
*/
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.
*
* @return bool
*/
pub fn is_none(self) -> bool {
self == Self::None
}
/**
* Check if module name is a filename with suffix 'ko'
*
* @return bool
*/
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.6.5/src/kernel/info.rs 0000644 0000000 0000000 00000003417 10461020230 0014156 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.
*
* @return KernelInfo
*/
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.
*
* @return Iterator
*/
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.6.5/src/kernel/lkm.rs 0000644 0000000 0000000 00000025032 10461020230 0014003 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.
*
* @return Iter
*/
#[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,
}
impl ListArgs {
/**
* Create a new list arguments instance.
*
* @param ArgMatches
* @return ListArgs
*/
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),
}
}
}
/* 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.
*
* @param ListArgs
* @param Style
* @return KernelModules
*/
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 = format!("{} {}", module_name, 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].to_string().parse().unwrap_or(0)).to_string();
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 = module_list.clone();
self.list = module_list;
self.scroll_list(ScrollDirection::Top);
Ok(())
}
/**
* Get the current command using current module name.
*
* @return Command
*/
pub fn get_current_command(&self) -> Command {
self.command.get(&self.current_name)
}
/**
* Set the current module command and show confirmation message.
*
* @param module_command
* @param command_name
*/
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.
*
* @return command_executed
*/
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.
*
* @return cancelled
*/
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.
*
* @param mod_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.
*
* @param direction
*/
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.
*
* @param direction
* @param smooth_scroll
*/
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.6.5/src/kernel/log.rs 0000644 0000000 0000000 00000005466 10461020230 0014012 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.
*
* @return logs_updated
*/
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.
*
* @param area_height
* @param area_sub
* @return selected_output
*/
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.
*
* @param direction
* @param smooth_scroll
*/
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.6.5/src/kernel/mod.rs 0000644 0000000 0000000 00000001445 10461020230 0014001 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.
*
* @param ArgMatches
* @return Kernel
*/
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.6.5/src/lib.rs 0000644 0000000 0000000 00000043022 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.
*
* @param Terminal
* @param Kernel
* @param Events
* @return Result
*/
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.size());
{
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(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::Char('?')
| 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,
},
Key::Char('?') | Key::F(1) => {
app.show_help_message(&mut kernel.modules);
app.input_mode = InputMode::None;
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.
*
* @param Sender
* @param Key
*/
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.6.5/src/main.rs 0000644 0000000 0000000 00000001561 10461020230 0012665 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.
*
* @return Result
*/
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.6.5/src/style.rs 0000644 0000000 0000000 00000013544 10461020230 0013105 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.
*
* @param replace
* @return Unicode
*/
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.
*
* @param Symbol
* @return str
*/
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.
*
* @param args
* @return Style
*/
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.
*
* @param args
* @param arg_name
* @param default_color
* @return Color
*/
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.
*
* @return vector
*/
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.
*
* @param text
* @param placeholder
*/
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.
*
* @param text
* @param delimiter
* @param style
* @return vector
*/
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.
*
* @return usize
*/
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.6.5/src/util.rs 0000644 0000000 0000000 00000006060 10461020230 0012715 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.
*
* @param cmd
* @param cmd_args
* @return Result
*/
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
*
* @return Result
*/
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.6.5/src/widgets.rs 0000644 0000000 0000000 00000003025 10461020230 0013404 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());
}
}