From b973e36ebbd825ee9230b937acb5e3e955818461 Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Mon, 31 Aug 2026 14:19:21 +0200 Subject: [PATCH] partially revert "aaa" This partially reverts commit 43b0c0d9b1e9edfbc2f655e60e1d1fcda5125f94. --- programs/typed/Cargo.toml | 9 +++ programs/typed/src/main.rs | 156 +++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 programs/typed/Cargo.toml create mode 100644 programs/typed/src/main.rs diff --git a/programs/typed/Cargo.toml b/programs/typed/Cargo.toml new file mode 100644 index 0000000..43da614 --- /dev/null +++ b/programs/typed/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "typed" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap.workspace = true +crossterm = "0.29.0" +notify = "8" diff --git a/programs/typed/src/main.rs b/programs/typed/src/main.rs new file mode 100644 index 0000000..32a3e7b --- /dev/null +++ b/programs/typed/src/main.rs @@ -0,0 +1,156 @@ +#![feature(lock_value_accessors)] +use std::{ + io, + path::Path, + process::{Command, exit}, + sync::{ + Arc, Condvar, Mutex, + atomic::{AtomicBool, AtomicU32, Ordering}, + mpsc::channel, + }, + thread::{self}, +}; + +use clap::Parser; +use notify::{Event, RecursiveMode, Watcher}; + +use crate::cli::Cli; + +mod cli { + use clap::Parser; + + #[derive(Debug, Parser)] + pub struct Cli { + pub path: String, + #[arg(short, long, env)] + pub editor: Option, + #[arg(short, long, env)] + pub viewer: Option, + #[arg(short, long)] + pub no_edit: bool, + } +} + +// TODOs: +// - create empty file if file doesnt exist +// - compile to pdf +// - always ensure pdf exists +// - delete pdf by default on program close +fn main() -> std::io::Result<()> { + let args = Cli::parse(); + + let pdf_path = format!("{}.pdf", args.path); + let typ_path = format!("{}.typ", args.path); + + let mupdf_pid = Arc::new(AtomicU32::new(0)); + let editor_pid = Arc::new(AtomicU32::new(0)); + let pair = Arc::new((Mutex::new(false), Condvar::new())); + + let pdf_path_ = pdf_path.clone(); + let mupdf_pid_ = Arc::clone(&mupdf_pid); + let viewer = args.viewer.unwrap_or("mupdf-x11".to_string()); + + let pair_mupdf = pair.clone(); + thread::spawn(move || { + let mut mupdf = Command::new(&viewer); + mupdf.arg(&pdf_path_); + + loop { + let Ok(mut mupdf) = mupdf.spawn() else { + break; + }; + + mupdf_pid_.store(mupdf.id(), Ordering::Relaxed); + let wait_res = mupdf.wait(); + if let Ok(status) = wait_res + && status.success() + { + // we exited! + crossterm::terminal::disable_raw_mode().unwrap(); + // we can unwrap here, because crashing doesnt matter when we're exiting anyway, it'll just be less clean + pair_mupdf.0.set(true).unwrap(); + pair_mupdf.1.notify_all(); + break; + } + } + }); + + let pdf_path_ = pdf_path.clone(); + let mupdf_pid_ = Arc::clone(&mupdf_pid); + thread::spawn(move || { + let (tx, rx) = channel::>(); + let mut watcher = notify::recommended_watcher(tx).unwrap(); + + let _ = watcher.watch(Path::new(&pdf_path_), RecursiveMode::NonRecursive); + + loop { + let p = rx.recv(); + + match p { + Ok(Ok(e)) if e.kind.is_modify() || e.kind.is_create() => { + let mupdf_pid = mupdf_pid_.load(Ordering::Relaxed); + if mupdf_pid != 0 { + let mut kill = Command::new("kill"); + + let _ = kill.arg("-1").arg(mupdf_pid.to_string()).spawn(); + } + } + Ok(Ok(e)) if e.kind.is_remove() => { + let _ = watcher.watch(Path::new(&pdf_path_), RecursiveMode::NonRecursive); + } + _ => {} + } + } + }); + + // the editor? + let pair_editor = pair.clone(); + let editor_pid_ = editor_pid.clone(); + if let Some(editor) = args.editor + && !args.no_edit + { + thread::spawn(move || { + let Ok(mut editor) = Command::new(editor).arg(&typ_path).spawn() else { + pair_editor.0.set(true).unwrap(); + pair_editor.1.notify_all(); + return; + }; + editor_pid_.store(editor.id(), Ordering::Relaxed); + let _ = editor.wait(); + // we know the editor exited here! + pair_editor.0.set(true).unwrap(); + pair_editor.1.notify_all(); + }); + } + + let (done, cvar) = &*pair; + // result doesnt matter. + // poisoned? we exit + // done? we exit + // we'll complain about the poison tho + let res = done + .lock() + .and_then(|lock| cvar.wait_while(lock, |done| !*done)); + + let mupdf_pid = mupdf_pid.load(Ordering::Relaxed); + let mupdf_killer = Command::new("kill") + .arg("-9") + .arg(mupdf_pid.to_string()) + .spawn(); + let editor_pid = editor_pid.load(Ordering::Relaxed); + if editor_pid != 0 { + let editor_killer = Command::new("kill") + .arg("-9") + .arg(editor_pid.to_string()) + .spawn(); + let _ = editor_killer.unwrap().wait(); + } + let _ = mupdf_killer.unwrap().wait(); + + println!("goodbye :3"); + if res.is_err() { + println!("the waiter was poisoned btw :(") + } + + Ok(()) +}