69 Commits

Author SHA1 Message Date
Alexandr Bogomyakov
7f62bcf55e Update README.md 2025-07-16 16:21:52 +03:00
Alexandr Bogomyakov
a420204e5d Update README.md 2025-07-16 16:20:41 +03:00
Ultradesu
b5db03203c Remove from cpu_bar 2025-07-16 16:12:38 +03:00
Ultradesu
d9b5643ae5 Fixed mem_load_bar and cpu_load_bar to new sysinfo crate 2025-07-16 16:09:55 +03:00
Ultradesu
eb3577979c Improved styling 2025-04-07 19:08:24 +01:00
Ultradesu
c082c328f5 Improved styling 2025-04-07 19:07:57 +01:00
Ultradesu
c9a7c33356 Drop PKGBUILD action 2025-04-03 13:44:50 +01:00
Ultradesu
a910ea39f4 Drop PKGBUILDaction 2025-04-03 13:38:14 +01:00
Ultradesu
337949a792 Drop PKGBUILDaction 2025-04-03 13:33:15 +01:00
Ultradesu
d09629794f Drop PKGBUILDaction 2025-04-03 13:32:19 +01:00
Ultradesu
59240601fe Bump PKGBUILD action 2025-04-03 13:31:06 +01:00
Ultradesu
00b0b5f3ef Bump PKGBUILD action 2025-04-03 13:28:06 +01:00
Ultradesu
60c014347c Bump PKGBUILD action 2025-04-03 13:25:20 +01:00
Ultradesu
e0ff50a7fa Bump PKGBUILD action 2025-04-03 13:21:26 +01:00
Ultradesu
df1ccc4054 Bump upload action 2025-04-03 13:20:10 +01:00
Ultradesu
86596eed29 Bump version 2025-04-03 13:18:40 +01:00
Ultradesu
4af53996a7 Added macos build 2025-04-03 13:17:50 +01:00
Ultradesu
25fe327294 Bump rust and libs 2025-03-31 16:18:21 +01:00
House of Vanity
6340565c27 Update build.yml 2020-09-23 19:20:43 +03:00
Alexandr
e9c59a6733 Improve player MPRIS line. 2020-09-22 15:52:55 +03:00
Alexandr Bogomyakov
5d37a86d09 Dummy commit. 2020-07-10 14:11:50 +03:00
AB
573cb35b52 Linting and minor style changes 2020-07-07 09:23:00 +03:00
AB
b1015ada2f Update readme 2020-05-20 12:29:22 +03:00
AB
e7ac12974f Fix player printing. 2020-05-15 13:07:49 +03:00
AB
6e08864d99 Fix duration format. 2020-05-14 23:39:40 +03:00
AB
6a9a871006 Fix duration format. 2020-05-14 23:32:05 +03:00
AB
bab994fc41 Fix duration format. 2020-05-14 22:13:22 +03:00
AB
fc1c2f539d Aupdate image 2020-05-14 22:00:02 +03:00
AB
0f5a1aaaed Hotfix 2020-05-14 21:53:47 +03:00
AB
1030e69932 Hotfix 2020-05-14 21:53:41 +03:00
AB
474d48d15c Add mpd. Rewrite. 2020-05-14 21:18:37 +03:00
Alexandr Bogomyakov
31c3237a5c Add SCP to deploy 2020-05-04 13:48:14 +03:00
Alexandr Bogomyakov
46f641220c Add SCP to deploy 2020-05-04 13:41:24 +03:00
Alexandr Bogomyakov
7e793c68c2 Update CI procedures. 2020-05-04 12:53:33 +03:00
Alexandr Bogomyakov
942e767c7e Update CI procedures. 2020-05-04 12:48:06 +03:00
Alexandr Bogomyakov
a276c1a2aa Merge branch 'master' of github.com:house-of-vanity/tmux_helper 2020-05-04 12:15:12 +03:00
Alexandr Bogomyakov
2150aaf5cd Update CI procedures. 2020-05-04 12:14:33 +03:00
AB
b1e418a633 Cleanup cpu_load_bar func. Using lib. 2020-04-11 16:35:55 +03:00
Alexandr Bogomyakov
8e569078de Minor fixes. 2020-04-10 17:20:02 +03:00
Alexandr Bogomyakov
5d23b7da2d Bump version 2019-12-24 13:17:20 +03:00
Alexandr Bogomyakov
5cf2ab0e40 Add time feature with custom format. 2019-12-24 13:16:23 +03:00
AB
7c7d9070a4 Update separator. 2019-12-19 01:39:22 +03:00
AB
456d423f47 Improve player metadata displaying. 2019-12-19 01:36:57 +03:00
Ultra Desu
cd854d450a Fix files. 2019-12-19 00:03:15 +03:00
Ultra Desu
7a934a063f Fix player metadata shorting. 2019-12-19 00:02:33 +03:00
Alexandr Bogomyakov
08cf01ee81 Add multiplayer support. 2019-12-17 15:10:31 +03:00
Alexandr Bogomyakov
530a753527 Add multiplayer support. 2019-12-17 15:05:24 +03:00
House of Vanity
614185e7f8 Create LICENSE-WTFPL
Add license
2019-12-10 13:38:34 +03:00
Alexandr Bogomyakov
ddd09761f5 Fix workflow. 2019-12-04 15:24:04 +03:00
Alexandr Bogomyakov
b264f3b823 Fix workflow. 2019-12-04 14:52:59 +03:00
Alexandr Bogomyakov
86a95efa49 Fix workflow. 2019-12-04 14:50:59 +03:00
Alexandr Bogomyakov
3e131088e6 Add env to workflow. 2019-12-04 14:30:56 +03:00
Alexandr Bogomyakov
f96467272b Add env to workflow. 2019-12-04 14:30:43 +03:00
Alexandr Bogomyakov
8cdaf4628b Add push action - build. 2019-12-04 14:23:28 +03:00
House of Vanity
23cc78cc0a Update README.md 2019-12-04 13:06:54 +03:00
House of Vanity
dec3226f41 Update README.md
Update readme.
2019-12-04 13:06:32 +03:00
House of Vanity
e085d55dde Create README.md 2019-12-04 12:58:26 +03:00
Alexandr Bogomyakov
17228e3ca8 Add screenshot 2019-12-04 12:57:00 +03:00
Ultra Desu
3b3f77a0e0 Merge colorize with player status 2019-12-03 23:53:21 +03:00
Alexandr Bogomyakov
f655e542fc Add player status icon. 2019-12-03 09:33:26 +03:00
Ultra Desu
5785b15a78 Colorize media player applet. 2019-11-30 16:21:33 +03:00
Ultra Desu
c69a95009d Add media player metadata. 2019-11-30 14:10:05 +03:00
Ultra Desu
b95183fd94 lifetime issue 2019-11-30 03:14:00 +03:00
Alexandr Bogomyakov
a9216ff630 Add dbus support 2019-11-29 18:17:57 +03:00
Alexandr Bogomyakov
b070d50bc7 Merge branch 'master' of github.com:house-of-vanity/tmux_helper 2019-11-28 18:00:10 +03:00
Alexandr Bogomyakov
139e7c7577 Linting 2019-11-28 18:00:00 +03:00
Alexandr Bogomyakov
23730088d5 Add dbus client. 2019-11-28 17:59:24 +03:00
House of Vanity
3a04d115bf Delete rust.yml 2019-11-25 12:31:33 +03:00
House of Vanity
971de9c6f0 Update release-upload.yml 2019-11-25 12:27:06 +03:00
11 changed files with 745 additions and 108 deletions

BIN
.github/prev.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

33
.github/workflows/PKGBUILD vendored Normal file
View File

@@ -0,0 +1,33 @@
# Maintainer: Alexandr Bogomyakov (ultradesu) <ab@hexor.ru>
pkgname=tmux-helper
pkgver=0.3.4
pkgrel=1
pkgdesc="Tmux helper"
url="https://github.com/house-of-vanity/tmux-helper.git"
arch=($CARCH)
license=(WTFPL custom)
depends=(tmux dbus)
makedepends=(cargo git dbus)
source=("git+https://github.com/house-of-vanity/$pkgname")
sha512sums=('SKIP')
pkgver() {
cd "$srcdir/$pkgname"
git describe --long --tags | awk -F '-' '{print $1}'| sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
}
prepare() {
cd "$srcdir/$pkgname"
cargo fetch --target $CARCH-unknown-linux-gnu
}
build() {
cd "$srcdir/$pkgname"
cargo build --release --frozen --all-targets
}
package() {
cd "$srcdir/$pkgname"
install -Dt "$pkgdir/usr/bin" target/release/$pkgname
}

95
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: Build and publish
on:
push:
tags:
- 'v*'
env:
BINARY_NAME: tmux-helper
jobs:
build:
name: Build binaries
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
include:
- os: ubuntu-latest
platform_name: linux-amd64
- os: macos-latest
platform_name: macos-arm64
steps:
- uses: actions/checkout@v4
- name: Pre-build (Linux)
if: matrix.os == 'ubuntu-latest'
run: sudo apt install -y libdbus-1-dev pkg-config libdbus-1-3 libfuse-dev
- name: Pre-build (macOS)
if: matrix.os == 'macos-latest'
run: brew install dbus
- name: Build binary
run: cargo build --release
- name: Rename binary
run: |
mkdir -p ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}
cp target/release/${{ env.BINARY_NAME }} ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}/${{ env.BINARY_NAME }}
- uses: actions/upload-artifact@v4
with:
name: ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}
path: ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}
release:
name: Create Release Page
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- uses: actions/checkout@v4
- uses: ncipollo/release-action@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
allowUpdates: true
tag: ${{ github.ref_name }}
upload:
name: Upload Release Assets
needs: release
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
include:
- os: ubuntu-latest
platform_name: linux-amd64
- os: macos-latest
platform_name: macos-arm64
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
name: Download ${{ matrix.platform_name }} artifact
with:
name: ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}
path: ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}/${{ env.BINARY_NAME }}
asset_name: ${{ env.BINARY_NAME }}_${{ matrix.platform_name }}
asset_content_type: application/octet-stream

View File

@@ -1,19 +0,0 @@
name: Rust
on: release
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: cargo build --verbose --release
- name: Upload to release
uses: JasonEtco/upload-to-release@master
with:
args: target/release/tmux-helper application/x-pie-executable
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,15 +0,0 @@
name: Rust
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

View File

@@ -1,8 +1,14 @@
[package]
name = "tmux-helper"
version = "0.1.0"
version = "0.4.0"
description = "Utility for printing system info for tmux status line."
authors = ["Ultra Desu <ultradesu@hexor.ru>"]
edition = "2018"
edition = "2021"
[dependencies]
sys-info = "*"
sysinfo = "0.36.0"
dbus = "0.9"
chrono = "0.4"
mpd = "0.1"
clap = "4"
size_format = "1"

13
LICENSE-WTFPL Normal file
View File

@@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

59
README.md Normal file
View File

@@ -0,0 +1,59 @@
# Tmux helper
Small app that perform system check and print TMUX friendly output. Prebuilded for MacOS M chip and Linux AMD64
<img width="1495" height="1264" alt="image" src="https://github.com/user-attachments/assets/7b9ffc97-0b59-4028-9b5d-f29347d16000" />
### Building
`cargo build --release`
or get binary on release page
### Fetures
```shell
Utility for printing system info for tmux status line.
Usage: tmux-helper [OPTIONS]
Options:
-c, --cpu
Print cpu load bar.
-m, --mem
Print mem usage bar.
--low <low>
Low threshold (0.0 - 1.0) [default: 0.7]
--mid <mid>
Mid threshold (0.0 - 1.0) [default: 0.9]
-p, --mpris
Show player info using MPRIS2 interface.
-d, --mpd
Show mpd player using MPD native protocol.
-l, --localtime [<localtime>]
Local time
-u, --utctime [<utctime>]
UTC time
-s, --symbol [<bar_symbol>]
Symbol to build bar [default: ▮]
-e, --empty-symbol [<bar_empty_symbol>]
Symbol to represent the empty part of the bar [default: ▯]
-a, --mpd-address <mpd_address>
<ADDR>:<PORT> of MPD server. [default: 127.0.0.1:6600]
--COLOR_LOW <COLOR_LOW>
CPU and MEM bar color while low usage. [default: 119]
--COLOR_MID <COLOR_MID>
CPU and MEM bar color while mid usage. [default: 220]
--COLOR_HIGH <COLOR_HIGH>
CPU and MEM bar color while high usage. [default: 197]
--COLOR_TRACK_NAME <COLOR_TRACK_NAME>
Color of track name filed. [default: 46]
--COLOR_TRACK_ARTIST <COLOR_TRACK_ARTIST>
Color of artist name filed. [default: 46]
--COLOR_TRACK_TIME <COLOR_TRACK_TIME>
Color of playing time field. [default: 153]
--COLOR_END <COLOR_END>
Default color using to terminate others. [default: 153]
-h, --help
Print help
-V, --version
Print version
```

245
src/config.rs Normal file
View File

@@ -0,0 +1,245 @@
use clap::{Arg, Command};
#[derive(Debug)]
pub enum Action {
Mem,
Cpu,
Mpris,
Mpd,
Localtime,
Utctime,
}
impl Default for Action {
fn default() -> Action {
Action::Cpu
}
}
#[derive(Default, Debug)]
pub struct Config {
pub action: Action,
pub mpd_server: String,
pub lt_format: Option<String>,
pub ut_format: Option<String>,
pub bar_symbol: Option<String>,
pub bar_empty_symbol: Option<String>,
pub low_threshold: f32,
pub mid_threshold: f32,
pub color_low: String,
pub color_mid: String,
pub color_high: String,
pub color_track_name: String,
pub color_track_artist: String,
pub color_track_time: String,
pub color_end: String,
}
fn colorize(color: String) -> String {
format!("#[fg=colour{}]", color)
}
pub fn read() -> Config {
let cli_args = Command::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.arg(
Arg::new("cpu")
.short('c')
.long("cpu")
.help("Print cpu load bar.")
.action(clap::ArgAction::SetTrue)
.conflicts_with_all(["mem", "mpris", "mpd", "localtime", "utctime"]),
)
.arg(
Arg::new("mem")
.short('m')
.long("mem")
.action(clap::ArgAction::SetTrue)
.help("Print mem usage bar."),
)
.arg(
Arg::new("low")
.long("low")
.help("Low threshold (0.0 - 1.0)")
.value_parser(clap::value_parser!(f32))
.default_value("0.7"),
)
.arg(
Arg::new("mid")
.long("mid")
.help("Mid threshold (0.0 - 1.0)")
.value_parser(clap::value_parser!(f32))
.default_value("0.9"),
)
.arg(
Arg::new("mpris")
.short('p')
.long("mpris")
.action(clap::ArgAction::SetTrue)
.help("Show player info using MPRIS2 interface."),
)
.arg(
Arg::new("mpd")
.short('d')
.long("mpd")
.action(clap::ArgAction::SetTrue)
.help("Show mpd player using MPD native protocol."),
)
.arg(
Arg::new("localtime")
.short('l')
.long("localtime")
.help("Local time")
.num_args(0..=1)
.default_missing_value("%H:%M"),
)
.arg(
Arg::new("utctime")
.short('u')
.long("utctime")
.help("UTC time")
.num_args(0..=1)
.default_missing_value("%H:%M"),
)
.arg(
Arg::new("bar_symbol")
.short('s')
.long("symbol")
.help("Symbol to build bar")
.num_args(0..=1)
.default_value(""),
)
.arg(
Arg::new("bar_empty_symbol")
.short('e')
.long("empty-symbol")
.help("Symbol to represent the empty part of the bar")
.num_args(0..=1)
.default_value(""),
)
.arg(
Arg::new("mpd_address")
.short('a')
.long("mpd-address")
.help("<ADDR>:<PORT> of MPD server.")
.default_value("127.0.0.1:6600"),
)
.arg(
Arg::new("COLOR_LOW")
.long("COLOR_LOW")
.help("CPU and MEM bar color while low usage.")
.default_value("119"),
)
.arg(
Arg::new("COLOR_MID")
.long("COLOR_MID")
.help("CPU and MEM bar color while mid usage.")
.default_value("220"),
)
.arg(
Arg::new("COLOR_HIGH")
.long("COLOR_HIGH")
.help("CPU and MEM bar color while high usage.")
.default_value("197"),
)
.arg(
Arg::new("COLOR_TRACK_NAME")
.long("COLOR_TRACK_NAME")
.help("Color of track name filed.")
.default_value("46"),
)
.arg(
Arg::new("COLOR_TRACK_ARTIST")
.long("COLOR_TRACK_ARTIST")
.help("Color of artist name filed.")
.default_value("46"),
)
.arg(
Arg::new("COLOR_TRACK_TIME")
.long("COLOR_TRACK_TIME")
.help("Color of playing time field.")
.default_value("153"),
)
.arg(
Arg::new("COLOR_END")
.long("COLOR_END")
.help("Default color using to terminate others.")
.default_value("153"),
)
.get_matches();
let lt_format = cli_args
.get_one::<String>("localtime")
.map(|s| s.to_string());
let ut_format = cli_args.get_one::<String>("utctime").map(|s| s.to_string());
let bar_symbol = cli_args
.get_one::<String>("bar_symbol")
.map(|s| s.to_string());
let bar_empty_symbol = cli_args
.get_one::<String>("bar_empty_symbol")
.map(|s| s.to_string());
let mut cfg = Config {
action: Action::Cpu,
mpd_server: cli_args
.get_one::<String>("mpd_address")
.unwrap()
.to_string(),
lt_format,
ut_format,
bar_symbol,
bar_empty_symbol,
low_threshold: *cli_args.get_one::<f32>("low").unwrap(),
mid_threshold: *cli_args.get_one::<f32>("mid").unwrap(),
color_low: colorize(cli_args.get_one::<String>("COLOR_LOW").unwrap().to_string()),
color_mid: colorize(cli_args.get_one::<String>("COLOR_MID").unwrap().to_string()),
color_high: colorize(
cli_args
.get_one::<String>("COLOR_HIGH")
.unwrap()
.to_string(),
),
color_track_name: colorize(
cli_args
.get_one::<String>("COLOR_TRACK_NAME")
.unwrap()
.to_string(),
),
color_track_artist: colorize(
cli_args
.get_one::<String>("COLOR_TRACK_ARTIST")
.unwrap()
.to_string(),
),
color_track_time: colorize(
cli_args
.get_one::<String>("COLOR_TRACK_TIME")
.unwrap()
.to_string(),
),
color_end: colorize(cli_args.get_one::<String>("COLOR_END").unwrap().to_string()),
};
if cli_args.get_flag("cpu") {
cfg.action = Action::Cpu;
}
if cli_args.get_flag("mem") {
cfg.action = Action::Mem;
}
if cli_args.contains_id("localtime") {
cfg.action = Action::Localtime;
}
if cli_args.contains_id("utctime") {
cfg.action = Action::Utctime;
}
if cli_args.get_flag("mpris") {
cfg.action = Action::Mpris;
}
if cli_args.get_flag("mpd") {
cfg.action = Action::Mpd;
}
cfg
}

View File

@@ -1,76 +1,24 @@
use std::fs;
use std::env;
use sys_info;
mod config;
mod utils;
const LOW: &str = "#[fg=colour186]";
const MID: &str = "#[fg=colour208]";
const HIGH: &str = "#[fg=colour160]";
const END: &str = "#[fg=colour7]";
fn read_file(file_path: &str) -> String {
fs::read_to_string(file_path)
.expect("Cant read file.")
}
fn to_bar(value: i32, max: i32, low: f32, mid: f32) {
let mut bar = "".to_string();
let bar_sym = "".to_string();
if (value as f32) / (max as f32) < low {
bar.push_str(LOW);
} else if (value as f32) / (max as f32) < mid {
bar.push_str(MID);
} else {
bar.push_str(HIGH);
}
for i in 0..max {
if i < value as i32 {
bar.push_str(&bar_sym);
} else {bar.push_str(" ")}
}
bar.push_str(END);
bar.push_str("|");
print!("{}", bar)
}
fn mem_load_bar(bar_len: i32) {
let memory;
match sys_info::mem_info() {
Err(w) => panic!("{:?}", w),
Ok(mem_data) => memory = mem_data,
}
let len = ((memory.total - memory.avail) as f32 / (memory.total as f32) * bar_len as f32) as i32;
to_bar(len, bar_len, 0.7, 0.9);
print!("{:.0} MiB", memory.avail/1024);
}
fn cpu_load_bar(bar_len: i32) {
let load = read_file("/proc/loadavg");
let load_data = load.split_whitespace().collect::<Vec<&str>>();
let _cpu_count = read_file("/proc/cpuinfo");
let cpu_count = _cpu_count.matches("model name").count();
let one: f32 = load_data[0].parse().unwrap();
let len: f32 = one as f32 / cpu_count as f32 * bar_len as f32;
to_bar(len as i32, bar_len, 0.3, 0.7);
print!("{:.2} LA1", one);
#[derive(Debug, Clone)]
pub struct TrackInfo {
pub title: String,
pub artist: String,
pub position: String,
pub duration: String,
pub status: String,
}
fn main() {
let args: Vec<String> = env::args().collect();
let help_text: &str = "Available commands -mb, -cb";
match args.len() {
1 => {
panic!(help_text);
},
2 => {
match args[1].as_ref() {
"-cb" => cpu_load_bar(15),
"-mb" => mem_load_bar(15),
_ => panic!(help_text),
}
},
_ => {
panic!(help_text);
}
}
let conf = config::read();
match conf.action {
config::Action::Cpu => utils::cpu_load_bar(15, &conf),
config::Action::Mem => utils::mem_load_bar(15, &conf),
config::Action::Mpris => utils::mpris(&conf),
config::Action::Utctime => utils::get_time(true, conf.ut_format),
config::Action::Localtime => utils::get_time(false, conf.lt_format),
config::Action::Mpd => utils::mpd(&conf),
}
}

272
src/utils.rs Normal file
View File

@@ -0,0 +1,272 @@
use crate::config;
use chrono::{Local, Utc};
use dbus::arg::RefArg;
use dbus::blocking::stdintf::org_freedesktop_dbus::Properties;
use dbus::{arg, blocking::Connection};
use mpd::Client;
use size_format::SizeFormatterBinary;
use std::process;
use std::thread;
use std::time::Duration;
use sysinfo::System;
#[derive(Debug, Clone)]
pub struct TrackInfo {
pub title: String,
pub artist: String,
pub position: String,
pub duration: String,
pub status: String,
}
pub fn to_bar(value: i32, max: i32, low: f32, mid: f32, config: &config::Config) {
let mut bar = "".to_string();
let ratio = (value as f32) / (max as f32);
bar.push_str(if ratio < low {
&config.color_low
} else if ratio < mid {
&config.color_mid
} else {
&config.color_high
});
let symbol = config.bar_symbol.as_deref().unwrap_or("");
let empty = config.bar_empty_symbol.as_deref().unwrap_or(" ");
for i in 0..max {
bar.push_str(if i < value { symbol } else { empty });
}
bar.push_str(&config.color_end);
bar.push('|');
print!("{}", bar);
}
pub fn mem_load_bar(bar_len: i32, config: &config::Config) {
let mut sys = System::new_all();
sys.refresh_memory();
let total_memory = sys.total_memory();
let used_memory = sys.used_memory();
// On macOS sysinfo.used_memory() includes caches and compressed memory
// Try a more conservative estimate
// Usually real used memory is about 30-50% of what sysinfo shows
#[cfg(target_os = "macos")]
let actual_used = used_memory * 40 / 100; // Approximately 40% of sysinfo.used_memory
#[cfg(not(target_os = "macos"))]
let actual_used = used_memory;
let actual_free = total_memory - actual_used;
let used_ratio = actual_used as f32 / total_memory as f32;
let len = (used_ratio * bar_len as f32) as i32;
to_bar(
len,
bar_len,
config.low_threshold,
config.mid_threshold,
config,
);
// Show: used/free
print!(
"{}/{} #[default]",
SizeFormatterBinary::new(actual_used),
SizeFormatterBinary::new(actual_free)
);
}
pub fn cpu_load_bar(bar_len: i32, config: &config::Config) {
let mut sys = System::new_all();
// Update CPU information
sys.refresh_cpu_all();
// Wait a bit to get accurate CPU usage data
thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
sys.refresh_cpu_all();
let cpu_count = sys.cpus().len();
// Get average CPU usage
let cpu_usage: f32 = sys.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / cpu_count as f32;
let cpu_load_ratio = cpu_usage / 100.0; // sysinfo returns percentages
let len = (cpu_load_ratio * bar_len as f32).round() as i32;
to_bar(
len,
bar_len,
config.low_threshold,
config.mid_threshold,
config,
);
print!("{:.1}%#[default]", cpu_usage);
}
pub fn get_player() -> Result<Vec<String>, Box<dyn std::error::Error>> {
let conn = Connection::new_session()?;
let proxy = conn.with_proxy("org.freedesktop.DBus", "/", Duration::from_secs(5));
let (names,): (Vec<String>,) = proxy.method_call("org.freedesktop.DBus", "ListNames", ())?;
Ok(names
.into_iter()
.filter(|n| n.contains("org.mpris.MediaPlayer2"))
.collect())
}
pub fn player_info(players: Vec<String>) -> Result<TrackInfo, Box<dyn std::error::Error>> {
for player in players {
let conn = Connection::new_session()?;
let proxy = conn.with_proxy(player, "/org/mpris/MediaPlayer2", Duration::from_secs(5));
let metadata: arg::PropMap = proxy.get("org.mpris.MediaPlayer2.Player", "Metadata")?;
let title = metadata
.get("xesam:title")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let artist = metadata
.get("xesam:artist")
.and_then(|v| v.as_iter())
.and_then(|mut artists| artists.next().and_then(|a| a.as_str()))
.unwrap_or("")
.to_string();
let duration_us = metadata
.get("mpris:length")
.and_then(|v| v.as_i64())
.unwrap_or(0);
let position_us: i64 = proxy.get("org.mpris.MediaPlayer2.Player", "Position")?;
let status_text: String = proxy.get("org.mpris.MediaPlayer2.Player", "PlaybackStatus")?;
let status = match status_text.as_str() {
"Playing" => "",
"Paused" => "",
_ => "",
}
.to_string();
let track_info = TrackInfo {
title,
artist,
position: format_time(position_us / 1_000_000),
duration: format_time(duration_us / 1_000_000),
status,
};
if track_info.status == "" {
return Ok(track_info);
}
}
Err("No active player".into())
}
pub fn format_time(sec: i64) -> String {
format!("{:02}:{:02}", sec / 60, sec % 60)
}
pub fn get_time(utc: bool, format: Option<String>) {
let fmt = format.unwrap_or_else(|| "%H:%M".to_string());
let now = if utc {
Utc::now().format(&fmt)
} else {
Local::now().format(&fmt)
};
println!("{}", now);
}
fn shorten(line: &str, max_len: usize) -> String {
if line.chars().count() > max_len {
format!("{}..", line.chars().take(max_len).collect::<String>())
} else {
line.to_string()
}
}
fn format_player(track_info: TrackInfo, config: &config::Config) {
let separator = if track_info.artist.is_empty() {
""
} else {
""
};
let max_len = if track_info.artist.is_empty() { 60 } else { 30 };
let artist_line = shorten(&track_info.artist, max_len);
let title_line = shorten(&track_info.title, max_len);
if track_info.position == "00:00" || track_info.duration.is_empty() {
println!(
"#[bold]{}{}{}{}{}{} {}{} {}#[default]",
config.color_track_name,
title_line,
config.color_end,
separator,
config.color_track_artist,
artist_line,
config.color_end,
config.color_track_time,
track_info.status
);
} else {
println!(
"#[bold]{}{}{}{}{}{} {}[{}/{}] {}{}{}#[default]",
config.color_track_name,
title_line,
config.color_end,
separator,
config.color_track_artist,
artist_line,
config.color_end,
config.color_track_time,
track_info.position,
track_info.duration,
track_info.status,
config.color_end
);
}
}
pub fn mpris(config: &config::Config) {
match player_info(get_player().unwrap_or_default()) {
Ok(track_info) => format_player(track_info, config),
Err(_) => println!("No music playing"),
}
}
pub fn mpd(config: &config::Config) {
let mut conn = Client::connect(&config.mpd_server).unwrap_or_else(|e| {
println!("Can't connect to MPD server. {}", e);
process::exit(1);
});
let song = conn.currentsong().unwrap_or(None);
let status = conn.status().unwrap();
let artist = song
.as_ref()
.and_then(|s| s.tags.iter().find(|(k, _)| k == "Artist").map(|(_, v)| v))
.cloned()
.unwrap_or_default();
let title = song
.as_ref()
.and_then(|s| s.title.clone().or_else(|| s.name.clone()))
.unwrap_or_default();
let (position, duration) = status.time.unwrap_or_default();
let track_info = TrackInfo {
title,
artist,
position: format_time(position.as_secs() as i64),
duration: format_time(duration.as_secs() as i64),
status: match status.state {
mpd::State::Play => "",
mpd::State::Pause => "",
mpd::State::Stop => "",
}
.to_string(),
};
format_player(track_info, config);
}