Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <SHELL>` flag, generate shell completion scripts | clap_complete (+ nushell) |

Expand Down
6 changes: 6 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
17 changes: 17 additions & 0 deletions docs/wayshot.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -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* = _"<string>"_

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 <screenshot directory>_

# SEE ALSO
- wayshot(1)
- wayshot(7)
Expand Down
4 changes: 2 additions & 2 deletions wayshot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <SHELL> CLI flag for generating shell completion scripts.
# Pulls in: clap_complete, clap_complete_nushell
Expand Down
8 changes: 8 additions & 0 deletions wayshot/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct Config {
pub base: Option<Base>,
pub file: Option<File>,
pub encoding: Option<Encoding>,
pub notification: Option<NotificationConfig>,
}

impl Default for Config {
Expand All @@ -19,6 +20,7 @@ impl Default for Config {
base: Some(Base::default()),
file: Some(File::default()),
encoding: Some(Encoding::default()),
notification: Some(NotificationConfig::default()),
}
}
}
Expand Down Expand Up @@ -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<String>,
}
57 changes: 52 additions & 5 deletions wayshot/src/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Comment thread
Gigas002 marked this conversation as resolved.
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) {
Expand Down
4 changes: 4 additions & 0 deletions wayshot/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

impl AppSettings {
Expand Down Expand Up @@ -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()),
}
}

Expand Down
6 changes: 5 additions & 1 deletion wayshot/src/wayshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down