use std::ffi::{CString, OsStr}; use std::io::Write; use std::os::unix::prelude::OsStrExt; use std::path::{Path, PathBuf}; use std::ptr::null_mut; use std::sync::atomic::AtomicBool; use std::time::Duration; use anyhow::{ensure, Context}; use directories::UserDirs; use git_version::git_version; use niri_config::Config; use smithay::output::Output; use smithay::reexports::rustix::time::{clock_gettime, ClockId}; use smithay::utils::{Logical, Point, Rectangle, Size}; pub mod spawning; pub mod watcher; pub static IS_SYSTEMD_SERVICE: AtomicBool = AtomicBool::new(false); pub fn clone2(t: (&T, &U)) -> (T, U) { (t.0.clone(), t.1.clone()) } pub fn version() -> String { format!( "{} ({})", env!("CARGO_PKG_VERSION"), git_version!(fallback = "unknown commit"), ) } pub fn get_monotonic_time() -> Duration { let ts = clock_gettime(ClockId::Monotonic); Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32) } pub fn center(rect: Rectangle) -> Point { rect.loc + rect.size.downscale(2).to_point() } pub fn output_size(output: &Output) -> Size { let output_scale = output.current_scale().integer_scale(); let output_transform = output.current_transform(); let output_mode = output.current_mode().unwrap(); output_transform .transform_size(output_mode.size) .to_logical(output_scale) } pub fn expand_home(path: &Path) -> anyhow::Result> { if let Ok(rest) = path.strip_prefix("~") { let dirs = UserDirs::new().context("error retrieving home directory")?; Ok(Some([dirs.home_dir(), rest].iter().collect())) } else { Ok(None) } } pub fn make_screenshot_path(config: &Config) -> anyhow::Result> { let Some(path) = &config.screenshot_path else { return Ok(None); }; let format = CString::new(path.clone()).context("path must not contain nul bytes")?; let mut buf = [0u8; 2048]; let mut path; unsafe { let time = libc::time(null_mut()); ensure!(time != -1, "error in time()"); let tm = libc::localtime(&time); ensure!(!tm.is_null(), "error in localtime()"); let rv = libc::strftime(buf.as_mut_ptr().cast(), buf.len(), format.as_ptr(), tm); ensure!(rv != 0, "error formatting time"); path = PathBuf::from(OsStr::from_bytes(&buf[..rv])); } if let Some(expanded) = expand_home(&path).context("error expanding ~")? { path = expanded; } Ok(Some(path)) } pub fn write_png_rgba8( w: impl Write, width: u32, height: u32, pixels: &[u8], ) -> Result<(), png::EncodingError> { let mut encoder = png::Encoder::new(w, width, height); encoder.set_color(png::ColorType::Rgba); encoder.set_depth(png::BitDepth::Eight); let mut writer = encoder.write_header()?; writer.write_image_data(pixels) } #[cfg(feature = "dbus")] pub fn show_screenshot_notification(image_path: Option) { let mut notification = notify_rust::Notification::new(); notification .summary("Screenshot captured") .body("You can paste the image from the clipboard.") .urgency(notify_rust::Urgency::Normal) .hint(notify_rust::Hint::Transient(true)); // Try to add the screenshot as an image if possible. if let Some(path) = image_path { match path.canonicalize() { Ok(path) => match url::Url::from_file_path(path) { Ok(url) => { notification.image_path(url.as_str()); } Err(err) => { warn!("error converting screenshot path to file url: {err:?}"); } }, Err(err) => { warn!("error canonicalizing screenshot path: {err:?}"); } } } if let Err(err) = notification.show() { warn!("error showing screenshot notification: {err:?}"); } } #[inline(never)] pub fn cause_panic() { let a = Duration::from_secs(1); let b = Duration::from_secs(2); let _ = a - b; }