diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 2e60740..b06ad14 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -25,12 +25,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install system dependencies - if: matrix.arch == 'x86_64' - run: | - sudo apt-get update - sudo apt-get install -y libncurses-dev - - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: diff --git a/Cargo.lock b/Cargo.lock index 06d072c..c719cab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -48,7 +48,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -57,6 +57,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + [[package]] name = "byteorder" version = "1.5.0" @@ -64,13 +70,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "cc" -version = "1.2.1" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" -dependencies = [ - "shlex", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" @@ -118,6 +121,83 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "heck" version = "0.5.0" @@ -132,18 +212,47 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] -name = "ncurses" -version = "6.0.1" -source = "git+https://github.com/jeaye/ncurses-rs#70023d5e41ae704f61849e8e6c7055eaad398bb6" +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ - "cc", "libc", - "pkg-config", + "log", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -174,10 +283,27 @@ dependencies = [ ] [[package]] -name = "pkg-config" -version = "0.3.31" +name = "parking_lot" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] [[package]] name = "primal-check" @@ -206,6 +332,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustfft" version = "6.2.0" @@ -222,10 +366,66 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "1.3.0" +name = "rustix" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "spectrust" @@ -233,7 +433,7 @@ version = "0.2.1" dependencies = [ "byteorder", "clap", - "ncurses", + "crossterm", "rustfft", ] @@ -276,6 +476,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + [[package]] name = "utf8parse" version = "0.2.2" @@ -288,6 +494,40 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.59.0" @@ -297,6 +537,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index fa8d402..5c2b7b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,5 @@ codegen-units = 1 [dependencies] byteorder = "1.5.0" clap = { version = "4.5.21", features = ["derive"] } -ncurses = "6.0.1" +crossterm = "0.29.0" rustfft = "6.2.0" - -[patch.crates-io] -ncurses = { git = "https://github.com/jeaye/ncurses-rs" } diff --git a/src/main.rs b/src/main.rs index 883e249..9c18bf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,17 @@ use clap::Parser; -use ncurses::*; -use ncurses::{LcCategory, setlocale}; +use crossterm::{ + cursor::{Hide, MoveTo}, + event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, + execute, queue, + style::Print, + terminal::{ + self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, + disable_raw_mode, enable_raw_mode, + }, +}; use rustfft::{algorithm::Radix4, num_complex::Complex, Fft, FftDirection}; use std::collections::VecDeque; -use std::io::{stdin, Read}; +use std::io::{stdout, Read, Write}; use std::fs::File; use std::time::{Duration, Instant}; use byteorder::{LittleEndian, ReadBytesExt}; @@ -20,7 +28,7 @@ struct Cell { } struct ViewDiff { - changes: Vec<(i32, i32, char)>, + changes: Vec<(u16, u16, char)>, } impl ViewDiff { @@ -33,13 +41,13 @@ impl ViewDiff { } fn add_change(&mut self, y: usize, x: usize, character: char) { - self.changes.push((y as i32, x as i32, character)); + self.changes.push((y as u16, x as u16, character)); } fn apply(&self) { - let mut buf = [0u8; 4]; + let mut out = stdout(); for &(y, x, ch) in &self.changes { - let _ = mvprintw(y, x, ch.encode_utf8(&mut buf)); + let _ = queue!(out, MoveTo(x, y), Print(ch)); } } } @@ -56,14 +64,14 @@ fn render_bar(buf: &mut Vec>, col: usize, sr: usize, bar_height_f: f32 } fn handle_terminal_resize( - rows: &mut i32, - cols: &mut i32, - new_rows: i32, - new_cols: i32, - max_width: i32, - max_height: i32, - safe_rows: &mut i32, - safe_cols: &mut i32, + rows: &mut u16, + cols: &mut u16, + new_rows: u16, + new_cols: u16, + max_width: u16, + max_height: u16, + safe_rows: &mut u16, + safe_cols: &mut u16, current_buffer: &mut Vec>, next_buffer: &mut Vec>, last_bars: &mut Vec, @@ -95,8 +103,9 @@ fn handle_terminal_resize( freq_mapping.push(index.min(fft_size / 2 - 1)); } - clear(); - refresh(); + let mut out = stdout(); + let _ = queue!(out, Clear(ClearType::All)); + let _ = out.flush(); } #[derive(Parser)] @@ -129,16 +138,18 @@ struct Cli { fn main() { let cli = Cli::parse(); - let _ = setlocale(LcCategory::all, ""); - initscr(); - noecho(); - curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE); - keypad(stdscr(), true); - timeout(0); + enable_raw_mode().expect("failed to enable raw mode"); + let mut out = stdout(); + execute!(out, EnterAlternateScreen, Hide).expect("failed to initialize terminal"); - let mut rows: i32 = 0; - let mut cols: i32 = 0; - getmaxyx(stdscr(), &mut rows, &mut cols); + let default_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + let _ = disable_raw_mode(); + let _ = execute!(stdout(), LeaveAlternateScreen); + default_hook(info); + })); + + let (mut cols, mut rows) = terminal::size().expect("failed to get terminal size"); let sample_rate: usize = 48000; let bit_depth: usize = 16; @@ -161,26 +172,26 @@ fn main() { PipeWire(std::process::ChildStdout), } - refresh(); + let _ = out.flush(); let mut audio_source = if cli.pipewire { let process = Command::new("pw-record") .args(["--format=s16", "--rate=48000", "--channels=2", "--raw", "-"]) .stdout(Stdio::piped()) .spawn() - .expect("Failed to start pw-record"); - let stdout = process.stdout.expect("Failed to capture pw-record stdout"); - refresh(); - AudioSource::PipeWire(stdout) + .expect("failed to start pw-record"); + let pipe = process.stdout.expect("failed to capture pw-record stdout"); + let _ = out.flush(); + AudioSource::PipeWire(pipe) } else if let Some(filename) = &cli.input { - let file = File::open(filename).expect("Failed to open input file"); + let file = File::open(filename).expect("failed to open input file"); AudioSource::File(file) } else { - AudioSource::StdIn(stdin().lock()) + AudioSource::StdIn(std::io::stdin().lock()) }; - let max_width = 1000; - let max_height = 1000; + let max_width: u16 = 1000; + let max_height: u16 = 1000; let mut safe_cols = cols.min(max_width); let mut safe_rows = rows.min(max_height); @@ -215,38 +226,44 @@ fn main() { let mut max_magnitude: f32 = 1.0; loop { - if last_terminal_resize.elapsed() >= resize_check_interval { - let mut new_rows = 0; - let mut new_cols = 0; - getmaxyx(stdscr(), &mut new_rows, &mut new_cols); - if new_rows != rows || new_cols != cols { - handle_terminal_resize( - &mut rows, &mut cols, new_rows, new_cols, - max_width, max_height, &mut safe_rows, &mut safe_cols, - &mut current_buffer, &mut next_buffer, - &mut last_bars, &mut freq_bands, &mut freq_mapping, - cli.log_power, fft_size, - ); - sc = safe_cols as usize; - sr = safe_rows as usize; + if event::poll(Duration::ZERO).unwrap_or(false) { + match event::read() { + Ok(Event::Key(KeyEvent { code: KeyCode::Char('q'), .. })) => break, + Ok(Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + })) => break, + Ok(Event::Resize(new_cols, new_rows)) => { + handle_terminal_resize( + &mut rows, &mut cols, new_rows, new_cols, + max_width, max_height, &mut safe_rows, &mut safe_cols, + &mut current_buffer, &mut next_buffer, + &mut last_bars, &mut freq_bands, &mut freq_mapping, + cli.log_power, fft_size, + ); + sc = safe_cols as usize; + sr = safe_rows as usize; + } + _ => {} } - last_terminal_resize = Instant::now(); } - let ch = getch(); - if ch == KEY_RESIZE { - let mut new_rows = 0; - let mut new_cols = 0; - getmaxyx(stdscr(), &mut new_rows, &mut new_cols); - handle_terminal_resize( - &mut rows, &mut cols, new_rows, new_cols, - max_width, max_height, &mut safe_rows, &mut safe_cols, - &mut current_buffer, &mut next_buffer, - &mut last_bars, &mut freq_bands, &mut freq_mapping, - cli.log_power, fft_size, - ); - sc = safe_cols as usize; - sr = safe_rows as usize; + if last_terminal_resize.elapsed() >= resize_check_interval { + if let Ok((new_cols, new_rows)) = terminal::size() { + if new_rows != rows || new_cols != cols { + handle_terminal_resize( + &mut rows, &mut cols, new_rows, new_cols, + max_width, max_height, &mut safe_rows, &mut safe_cols, + &mut current_buffer, &mut next_buffer, + &mut last_bars, &mut freq_bands, &mut freq_mapping, + cli.log_power, fft_size, + ); + sc = safe_cols as usize; + sr = safe_rows as usize; + } + } + last_terminal_resize = Instant::now(); } if last_frame_time.elapsed() >= interval { @@ -261,7 +278,7 @@ fn main() { let read_result = match &mut audio_source { AudioSource::StdIn(input) => input.read(&mut input_buffer), AudioSource::File(file) => file.read(&mut input_buffer), - AudioSource::PipeWire(stdout) => stdout.read(&mut input_buffer), + AudioSource::PipeWire(pipe) => pipe.read(&mut input_buffer), }; match read_result { @@ -313,7 +330,7 @@ fn main() { } view_diff.apply(); std::mem::swap(&mut current_buffer, &mut next_buffer); - refresh(); + let _ = out.flush(); break; } } @@ -379,7 +396,7 @@ fn main() { view_diff.apply(); std::mem::swap(&mut current_buffer, &mut next_buffer); last_frame_time = Instant::now(); - refresh(); + let _ = out.flush(); } else if buffer_is_empty { for i in 0..sc { last_bars[i] *= drop_factor; @@ -397,8 +414,11 @@ fn main() { view_diff.apply(); std::mem::swap(&mut current_buffer, &mut next_buffer); last_frame_time = Instant::now(); - refresh(); + let _ = out.flush(); } } } + + let _ = execute!(out, LeaveAlternateScreen); + let _ = disable_raw_mode(); }