diff --git a/README.md b/README.md index 1ff25f27..ea400252 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ cargo build --no-default-features --features clipboard,logger,notifications | `color_picker` | `--color` flag, freeze screen and pick a pixel color | — | | `jxl` | JPEG-XL encoding (`--encoding` / `.jxl`) | libjxl / `jpegxl-rs` | | `logger` | `--log-level` flag, tracing output to stderr | tracing-subscriber | -| `notifications` | Desktop notifications after each capture | notify-rust | +| `notifications` | Desktop notifications after each capture; configurable click action | notify-rust, rustix | | `selector` | `--geometry` flag, interactive region selection | libwaysip | | `completions` | `--completions ` flag, generate shell completion scripts | clap_complete (+ nushell) | diff --git a/config.toml b/config.toml index 123d5581..bd6658a7 100644 --- a/config.toml +++ b/config.toml @@ -76,3 +76,9 @@ distance = 1 # For lossless, generally it will produce smaller files. # For lossy, higher effort should more accurately reach the target quality. effort = 7 + +[notification] +# Command to run when the notification is clicked. +# The command is run exactly as provided with no arguments appended. +# If not set, opens the screenshot directory with xdg-open. +# action = "xdg-open PATH" diff --git a/docs/wayshot.5.scd b/docs/wayshot.5.scd index 81eb600f..bb4ef619 100644 --- a/docs/wayshot.5.scd +++ b/docs/wayshot.5.scd @@ -222,6 +222,23 @@ This section documents the *[file]* table of the configuration file Default: _7_ +# NOTIFICATION + +This section documents the *[notification]* table of the configuration file. + +_Requires the_ *notifications* _feature (enabled by default)._ + +*action* = _""_ + + Command to execute when the user clicks the notification. + The command is run exactly as provided with no arguments appended. + If not set, opens the screenshot directory with xdg-open. + + Examples: + - action = "xdg-open /home/user/Pictures" -> opens a fixed folder + + Default: _xdg-open _ + # SEE ALSO - wayshot(1) - wayshot(7) diff --git a/wayshot/Cargo.toml b/wayshot/Cargo.toml index 2cc2e0f8..8ffa08ff 100644 --- a/wayshot/Cargo.toml +++ b/wayshot/Cargo.toml @@ -71,8 +71,8 @@ logger = ["dep:tracing-subscriber"] # Sends a desktop notification (via D-Bus / libnotify) after each capture. # Disable removes the --silent CLI flag. -# Pulls in: notify-rust -notifications = ["dep:notify-rust"] +# Pulls in: notify-rust, rustix +notifications = ["dep:notify-rust", "dep:rustix"] # Adds the --completions CLI flag for generating shell completion scripts. # Pulls in: clap_complete, clap_complete_nushell diff --git a/wayshot/src/config.rs b/wayshot/src/config.rs index 6987ea95..4b4cd176 100644 --- a/wayshot/src/config.rs +++ b/wayshot/src/config.rs @@ -11,6 +11,7 @@ pub struct Config { pub base: Option, pub file: Option, pub encoding: Option, + pub notification: Option, } impl Default for Config { @@ -19,6 +20,7 @@ impl Default for Config { base: Some(Base::default()), file: Some(File::default()), encoding: Some(Encoding::default()), + notification: Some(NotificationConfig::default()), } } } @@ -222,3 +224,9 @@ impl Png { } } } + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct NotificationConfig { + /// Shell command to run when the notification is clicked. + pub action: Option, +} diff --git a/wayshot/src/notification.rs b/wayshot/src/notification.rs index 2fb7ce50..d4547e06 100644 --- a/wayshot/src/notification.rs +++ b/wayshot/src/notification.rs @@ -2,23 +2,70 @@ use eyre::Error; use notify_rust::Notification; +use rustix::runtime::{self, Fork}; +use std::path::Path; +use std::process::Command; use crate::screenshot::ShotResult; const TIMEOUT_MS: i32 = 5000; -pub fn send_success(result: &ShotResult) { +pub fn send_success( + result: &ShotResult, + saved_location: Option<&Path>, + action_command: Option<&str>, +) { let body = match result { ShotResult::Output { name } => format!("Screenshot of output '{name}' saved"), ShotResult::Toplevel { name } => format!("Screenshot of toplevel '{name}' saved"), ShotResult::Area => "Screenshot of selected area saved".to_string(), ShotResult::All => "Screenshot of all outputs saved".to_string(), }; - let _ = Notification::new() + + let mut notification = Notification::new(); + notification .summary("Screenshot Taken") - .body(&body) - .timeout(TIMEOUT_MS) - .show(); + .appname("wayshot") + .timeout(TIMEOUT_MS); + + if let Some(path) = saved_location { + let dir_path = path + .parent() + .unwrap_or(Path::new(".")) + .to_string_lossy() + .to_string(); + + notification.action("open_location", "Open Folder"); + notification.action("default", "Open Folder"); + + match unsafe { runtime::kernel_fork() } { + Ok(Fork::Child(_)) => { + if let Ok(handle) = notification.show() { + handle.wait_for_action(|action| { + if action == "open_location" || action == "default" { + let cmd = match action_command { + Some(custom) => custom.to_string(), + None => format!("xdg-open {dir_path}"), + }; + let _ = Command::new("sh") + .args(["-c", &cmd]) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn(); + } + }); + } + std::process::exit(0); + } + Ok(Fork::ParentOf(_)) => {} + Err(e) => { + tracing::error!("Fork failed for notification action: {}", e); + let _ = notification.body(&body).show(); + } + } + } else { + let _ = notification.body(&body).show(); + } } pub fn send_failure(error: &Error) { diff --git a/wayshot/src/settings.rs b/wayshot/src/settings.rs index f2fba873..bd990612 100644 --- a/wayshot/src/settings.rs +++ b/wayshot/src/settings.rs @@ -51,6 +51,8 @@ pub(crate) struct AppSettings { pub(crate) clipboard: bool, #[cfg(feature = "notifications")] pub(crate) notifications: bool, + #[cfg(feature = "notifications")] + pub(crate) notification_action: Option, } impl AppSettings { @@ -149,6 +151,8 @@ impl AppSettings { clipboard: cli.clipboard || base.clipboard.unwrap_or_default(), #[cfg(feature = "notifications")] notifications: !cli.silent && base.notifications.unwrap_or(true), + #[cfg(feature = "notifications")] + notification_action: config.notification.as_ref().and_then(|n| n.action.clone()), } } diff --git a/wayshot/src/wayshot.rs b/wayshot/src/wayshot.rs index 271b1d10..b3229cc4 100644 --- a/wayshot/src/wayshot.rs +++ b/wayshot/src/wayshot.rs @@ -94,7 +94,11 @@ fn main() -> Result<()> { #[cfg(feature = "notifications")] if settings.notifications { - notification::send_success(&shot_result); + notification::send_success( + &shot_result, + settings.file.as_deref(), + settings.notification_action.as_deref(), + ); } // Silence unused warning when the notifications feature is disabled. #[cfg(not(feature = "notifications"))]