From b245c362c4a2c438417fb3a0911ba7430c40acc9 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Sun, 21 Feb 2021 06:52:09 -0600 Subject: [PATCH] Don't allow 2 processes of leanshot to run at the same time, using a lockfile. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/gui.rs | 9 ++++-- src/main.rs | 5 ++++ src/singleton.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 src/singleton.rs diff --git a/Cargo.lock b/Cargo.lock index 20a72ed..e6f7af7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "leanshot" -version = "0.4.1" +version = "0.5.0" dependencies = [ "anyhow", "image", diff --git a/Cargo.toml b/Cargo.toml index 1e0f67e..8acb5a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "leanshot" description = "Screenshot capture for Linux." -version = "0.4.1" +version = "0.5.0" repository = "https://git.mzhang.io/michael/leanshot" license-file = "LICENSE" edition = "2018" diff --git a/src/gui.rs b/src/gui.rs index e4d4a20..bd11f30 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -223,7 +223,12 @@ impl Gui { match evt.response_type() { xcb::KEY_RELEASE => { let evt = unsafe { xcb::cast_event::(&evt) }; - trace!("key released: root={} key={} state={}", evt.root(), evt.detail(), evt.state()); + trace!( + "key released: root={} key={} state={}", + evt.root(), + evt.detail(), + evt.state() + ); if evt.detail() == 9 { if state.dragging { @@ -255,7 +260,7 @@ impl Gui { // left mouse button if state.dragging && evt.detail() == 1 { - state.dragging = false; + state.dragging = false; if let Some(_) = state.rect() { break; } diff --git a/src/main.rs b/src/main.rs index 8dfeb2b..ec01cfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate log; extern crate anyhow; mod gui; +mod singleton; use std::path::PathBuf; use std::process; @@ -31,6 +32,10 @@ fn main() -> Result<()> { capture.save_to(&opt.outfile)?; } Region::Selection => { + info!("Creating lockfile..."); + let _lockfile = singleton::check_singleton()?; + info!("Lockfile: {:?}", _lockfile); + if let Some(rectangle) = gui.interactive_select(&capture)? { capture.save_cropped_to(&opt.outfile, Some(rectangle))?; } else { diff --git a/src/singleton.rs b/src/singleton.rs new file mode 100644 index 0000000..a33f0c9 --- /dev/null +++ b/src/singleton.rs @@ -0,0 +1,73 @@ +use std::fs::{File, self}; +use std::io::{Write, Read}; +use std::path::PathBuf; +use std::time::{SystemTime, Duration, UNIX_EPOCH}; +use std::process; + +use anyhow::{Error, Context, Result}; + +const PATHS: &[&str] = &["/var/run", "/tmp"]; + +pub fn check_singleton() -> Result { + let (mut file, path) = get_lock_file()?; + + let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); + let pid = process::id(); + let contents = format!("{}:{}", current_time, pid).as_bytes().to_vec(); + file.write_all(&contents)?; + + Ok(Lockfile(path)) +} + +#[derive(Debug)] +pub struct Lockfile(PathBuf); + +impl Drop for Lockfile { + fn drop(&mut self) { + info!("Dropping lockfile..."); + fs::remove_file(&self.0).unwrap(); + } +} + +fn get_lock_file() -> Result<(File, PathBuf)> { + for dir_path in PATHS.iter() { + use std::io::ErrorKind::*; + let dir_path = PathBuf::from(dir_path); + let lockfile_path = dir_path.join("leanshot.pid"); + + // check if it already exists + match File::open(&lockfile_path) { + Ok(mut f) => { + let mut contents = String::new(); + f.read_to_string(&mut contents)?; + + let mut parts = contents.split(":"); + let timestamp = parts.next().unwrap().parse::()?; + let pid = parts.next().unwrap(); + let time = UNIX_EPOCH + Duration::from_millis(timestamp); + + // if it hasn't been 5 minutes since the last open, then bail + if SystemTime::now() - Duration::from_secs(60 * 5) < time { + bail!("existing lockfile found for pid {} at time {:?}", pid, time); + } + } + // ignore errors + Err(e) => match e.kind() { + NotFound => {}, + _ => return Err(Error::from(e).context("could not open lockfile for reading")), + } + }; + + let file = match File::create(&lockfile_path) { + Ok(f) => f, + Err(e) => match e.kind() { + PermissionDenied => continue, + _ => return Err(Error::from(e).context("could not open lockfile for writing")), + }, + }; + + return Ok((file, lockfile_path)); + } + + bail!("could not find a suitable place to place lockfile"); +}