Add watchfile
This commit is contained in:
160
src/main.rs
160
src/main.rs
@ -1,10 +1,17 @@
|
||||
use std::process::Command;
|
||||
use terminal_size::{Width, Height, terminal_size};
|
||||
use colored::{Color, Colorize};
|
||||
use std::io::{self, Write, stdout};
|
||||
use std::thread::sleep;
|
||||
use hotwatch::Hotwatch;
|
||||
use std::fs::remove_file;
|
||||
use std::future::pending;
|
||||
use std::io::{stdout, BufWriter, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{self, Command};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use structopt::StructOpt;
|
||||
use terminal_size::{terminal_size, Height, Width};
|
||||
use tokio::sync::Notify;
|
||||
use tokio::time::sleep;
|
||||
use tokio::{fs::File, select, spawn};
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt()]
|
||||
@ -20,52 +27,43 @@ struct Opt {
|
||||
/// Set number of rows in column
|
||||
#[structopt(short, long)]
|
||||
timeout: Option<u64>,
|
||||
|
||||
/// Watch for changes on a file. If the file changes, restart the timeout.
|
||||
#[structopt(short, long)]
|
||||
watch: Option<PathBuf>,
|
||||
}
|
||||
|
||||
const COLORS: &[Color] = &[
|
||||
Color::Green,
|
||||
Color::Blue,
|
||||
Color::Yellow,
|
||||
Color::Red,
|
||||
];
|
||||
type Rendered = Vec<Vec<(char, Color)>>;
|
||||
|
||||
const BLOCK_CHARS: &[char] = &[
|
||||
' ',
|
||||
'▁',
|
||||
'▂',
|
||||
'▃',
|
||||
'▄',
|
||||
'▅',
|
||||
'▆',
|
||||
'▇',
|
||||
'█',
|
||||
];
|
||||
const COLORS: &[Color] = &[Color::Green, Color::Blue, Color::Yellow, Color::Red];
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let opt = Opt::from_args();
|
||||
const BLOCK_CHARS: &[char] = &[' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
||||
|
||||
fn get_volume() -> eyre::Result<f64> {
|
||||
let output = Command::new("pamixer").args(["--get-volume"]).output()?;
|
||||
|
||||
let out =
|
||||
std::str::from_utf8(&output.stdout).expect("failed to parse paxmixer output as utf-8");
|
||||
|
||||
let volume: u16 = out
|
||||
.trim()
|
||||
.parse()
|
||||
.expect("failed to parse pamixer output as u16");
|
||||
let percentage = volume as f64 / 100.0;
|
||||
Ok(percentage)
|
||||
}
|
||||
|
||||
fn render_bars(opt: &Opt, volume: f64) -> eyre::Result<Rendered> {
|
||||
let (Width(calc_max_cols), Height(calc_max_rows)) = terminal_size().unwrap();
|
||||
let calc_max_rows = calc_max_rows.min(4);
|
||||
|
||||
let max_cols = opt.cols.unwrap_or(calc_max_cols);
|
||||
let max_rows = opt.rows.unwrap_or(calc_max_rows);
|
||||
|
||||
|
||||
let output = Command::new("pamixer")
|
||||
.args(&["--get-volume"])
|
||||
.output()?;
|
||||
|
||||
let out = std::str::from_utf8(&output.stdout)
|
||||
.expect("failed to parse paxmixer output as utf-8");
|
||||
|
||||
let volume: u16 = out.trim().parse().expect("failed to parse pamixer output as u16");
|
||||
let percentage = volume as f64 / 100.0;
|
||||
|
||||
let num_cols = ((max_cols as f64 * percentage) as u16).min(max_cols);
|
||||
let num_cols = ((max_cols as f64 * volume) as u16).min(max_cols);
|
||||
let max_chars = max_cols / 2;
|
||||
let num_chars = num_cols / 2;
|
||||
|
||||
|
||||
let mut lines = vec![vec![]; max_rows as usize];
|
||||
|
||||
for i in 0..num_chars {
|
||||
@ -83,22 +81,98 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
lines.reverse();
|
||||
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
fn draw_bars(lines: &Rendered) -> eyre::Result<()> {
|
||||
let stdout = stdout();
|
||||
let mut handle = stdout.lock();
|
||||
let mut stdout = BufWriter::new(stdout.lock());
|
||||
|
||||
// clear console and more cursor to 0,0
|
||||
write!(&mut stdout, "{esc}[2J{esc}[1;1H", esc = 27 as char)?;
|
||||
|
||||
let mut char_buf = [0u8; 4];
|
||||
for line in &lines {
|
||||
writeln!(&mut handle)?;
|
||||
for line in lines {
|
||||
writeln!(&mut stdout)?;
|
||||
for &(bc, color) in line {
|
||||
let bc = bc.encode_utf8(&mut char_buf);
|
||||
write!(&mut handle, " {}", bc.color(color))?;
|
||||
let bc = bc.encode_utf8(&mut char_buf);
|
||||
write!(&mut stdout, " {}", bc.color(color))?;
|
||||
}
|
||||
}
|
||||
|
||||
handle.flush()?;
|
||||
stdout.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
let opt = Opt::from_args();
|
||||
color_eyre::install()?;
|
||||
|
||||
if let Some(path) = &opt.watch {
|
||||
let _file = File::options()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let mut volume = get_volume()?;
|
||||
|
||||
{
|
||||
let rendered = render_bars(&opt, volume)?;
|
||||
draw_bars(&rendered)?;
|
||||
}
|
||||
|
||||
let extend_timeout = Arc::new(Notify::new());
|
||||
if let Some(millis) = opt.timeout {
|
||||
sleep(Duration::from_millis(millis));
|
||||
let duration = Duration::from_millis(millis);
|
||||
let extend_timeout = Arc::clone(&extend_timeout);
|
||||
let watchfile_path = opt.watch.clone();
|
||||
spawn(async move {
|
||||
loop {
|
||||
select! {
|
||||
_ = extend_timeout.notified() => {}
|
||||
_ = sleep(duration) => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = watchfile_path {
|
||||
let _ = remove_file(path);
|
||||
}
|
||||
|
||||
process::exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(path) = &opt.watch {
|
||||
let mut watcher = Hotwatch::new()?;
|
||||
let render = Arc::new(Notify::new());
|
||||
|
||||
{
|
||||
let render = Arc::clone(&render);
|
||||
watcher.watch(path, move |_ev| {
|
||||
render.notify_waiters();
|
||||
extend_timeout.notify_waiters();
|
||||
})?;
|
||||
}
|
||||
|
||||
loop {
|
||||
render.notified().await;
|
||||
let new_volume = get_volume()?;
|
||||
if new_volume == volume {
|
||||
continue;
|
||||
}
|
||||
|
||||
volume = new_volume;
|
||||
let rendered = render_bars(&opt, volume)?;
|
||||
draw_bars(&rendered)?;
|
||||
}
|
||||
}
|
||||
|
||||
if opt.timeout.is_some() {
|
||||
return pending().await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user