use std::ffi::{CString, OsStr}; use std::io::{self, Write}; use std::os::unix::prelude::OsStrExt; use std::os::unix::process::CommandExt; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::ptr::null_mut; use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; use std::time::Duration; use anyhow::{ensure, Context}; use directories::UserDirs; use smithay::reexports::rustix::time::{clock_gettime, ClockId}; use smithay::utils::{Logical, Point, Rectangle}; use crate::config::Config; 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 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 Ok(rest) = path.strip_prefix("~") { let dirs = UserDirs::new().context("error retrieving home directory")?; path = [dirs.home_dir(), rest].iter().collect(); } Ok(Some(path)) } pub static REMOVE_ENV_RUST_BACKTRACE: AtomicBool = AtomicBool::new(false); pub static REMOVE_ENV_RUST_LIB_BACKTRACE: AtomicBool = AtomicBool::new(false); /// Spawns the command to run independently of the compositor. pub fn spawn + Send + 'static>(command: Vec) { let _span = tracy_client::span!(); if command.is_empty() { return; } // Spawning and waiting takes some milliseconds, so do it in a thread. let res = thread::Builder::new() .name("Command Spawner".to_owned()) .spawn(move || { let (command, args) = command.split_first().unwrap(); spawn_sync(command, args); }); if let Err(err) = res { warn!("error spawning a thread to spawn the command: {err:?}"); } } fn spawn_sync(command: impl AsRef, args: impl IntoIterator>) { let _span = tracy_client::span!(); let command = command.as_ref(); let mut process = Command::new(command); process .args(args) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()); // Remove RUST_BACKTRACE and RUST_LIB_BACKTRACE from the environment if needed. if REMOVE_ENV_RUST_BACKTRACE.load(Ordering::Relaxed) { process.env_remove("RUST_BACKTRACE"); } if REMOVE_ENV_RUST_LIB_BACKTRACE.load(Ordering::Relaxed) { process.env_remove("RUST_LIB_BACKTRACE"); } // Double-fork to avoid having to waitpid the child. unsafe { process.pre_exec(|| { match libc::fork() { -1 => return Err(io::Error::last_os_error()), 0 => (), _ => libc::_exit(0), } Ok(()) }); } let mut child = match process.spawn() { Ok(child) => child, Err(err) => { warn!("error spawning {command:?}: {err:?}"); return; } }; match child.wait() { Ok(status) => { if !status.success() { warn!("child did not exit successfully: {status:?}"); } } Err(err) => { warn!("error waiting for child: {err:?}"); } } } 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:?}"); } }